diff --git a/10-build-script.sh b/10-build-script.sh new file mode 100755 index 0000000000000000000000000000000000000000..78bbc0ba42931c8339de5b6734e3ee423c7d976d --- /dev/null +++ b/10-build-script.sh @@ -0,0 +1,77 @@ +#!/bin/bash -e + +banner_head() +{ + echo "+------------------------------------------+" + printf "| %-40s |\n" "`date`" + echo "| |" + printf "\033[35m|`tput bold``tput rev` %+30s `tput sgr0`\033[0m|\n" "$@" + echo "+------------------------------------------+" +} + +banner_head Developer-Console + +sleep 3 + +printf -- '\033[35m \x1b[1m ~ ~ ~ ~ ~ Installing all dependencies/library ~ ~ ~ ~ ~ \033[0m\n' +yarn --cwd ./developer-console-ui/app install +printf -- '\033[32m \x1b[1m ~ ~ ~ ~ ~ Installation is completed ~ ~ ~ ~ ~ \033[0m\n\n' + +printf -- '\033[35m \x1b[1m ~ ~ ~ ~ ~ dco-gateway build start ~ ~ ~ ~ ~ \033[0m\n' +yarn --cwd ./developer-console-ui/app build +printf -- '\033[32m \x1b[1m ~ ~ ~ ~ ~ developer-console-ui build successful ~ ~ ~ ~ ~ \033[0m\n\n' + +sleep 3 + +printf -- '\033[35m \x1b[1m ~ ~ ~ ~ ~ dco-gateway build start ~ ~ ~ ~ ~ \033[0m\n' +mvn clean verify \ +--batch-mode \ +--file dco-gateway/pom.xml \ +--settings dco-gateway/settings.xml +printf -- '\033[32m \x1b[1m ~ ~ ~ ~ ~ dco-gateway build successful ~ ~ ~ ~ ~ \033[0m\n\n' + +sleep 3 + +printf -- '\033[35m \x1b[1m ~ ~ ~ ~ ~ scenario-library-service build start ~ ~ ~ ~ ~ \033[0m\n' +mvn clean verify \ +--batch-mode \ +--file scenario-library-service/pom.xml \ +--settings scenario-library-service/settings.xml +printf -- '\033[32m \x1b[1m ~ ~ ~ ~ ~ scenario-library-service build successful~ ~ ~ ~ ~ \033[0m\n\n' + +sleep 3 + +printf -- '\033[35m \x1b[1m ~ ~ ~ ~ ~ tracks-management-service build start ~ ~ ~ ~ ~ \033[0m\n' +mvn clean verify \ +--batch-mode \ +--file tracks-management-service/pom.xml \ +--settings tracks-management-service/settings.xml +printf -- '\033[32m \x1b[1m ~ ~ ~ ~ ~ tracks-management-service build successful ~ ~ ~ ~ ~ \033[0m\n\n\n' + +sleep 3 + +printf -- '\033[35m \x1b[1m ~ ~ ~ ~ ~ Start Minio ~ ~ ~ ~ ~ \033[0m\n' +[ -e minio/minio_keys.env ] && rm -rf minio/minio_keys.env + +touch minio/minio_keys.env + +docker compose up -d minio +docker compose ls +docker compose ps +if [ $( docker ps -a | grep minio | wc -l ) -gt 0 ]; then + printf -- '\033[32m \x1b[1m ~ ~ ~ ~ ~ minio is running ~ ~ ~ ~ ~ \033[0m\n\n' + printf -- '\033[32m \x1b[1m You can access minio s3 using http://localhost:9001 url. + The admin user is "minioadmin" and default password is "minioadmin" \033[0m\n\n' + + tput rev + printf -- '\033[35m \x1b[1m Create 'dco-scenario-library-service' bucket and Access key, Secret Key in minio and run deploy script as per the documentation. \033[0m\n\n' + tput sgr0 +else + printf -- '\033[31m \x1b[1m ~ ~ ~ ~ ~ minio container is missing ~ ~ ~ ~ ~ \033[0m\n' +fi + +printf -- '\033[32m \x1b[1m For deployment, continue from step 3 from Build and Deploy section once you are done with Minio configuration \033[0m\n\n' + + + + diff --git a/20-deploy-script.sh b/20-deploy-script.sh new file mode 100755 index 0000000000000000000000000000000000000000..fae953948c5c5f27ae054fcfcfd10678014a03ee --- /dev/null +++ b/20-deploy-script.sh @@ -0,0 +1,60 @@ +#!/bin/bash -e + +help() +{ + # Display Help + echo + printf -- '\033[31m \x1b[1m ~ ~ ~ ~ ~ Wrong Syntax ~ ~ ~ ~ ~ \033[0m\n' + echo "Argument access-key or secrete-key is missing." + echo + printf -- '\033[32m \x1b[1m ~ ~ ~ ~ ~ Help ~ ~ ~ ~ ~ \033[0m\n' + echo "Syntax:" + echo "sh 20-deploy-script.sh <access-key> <secrete-key> " + echo + echo "Description:" + echo "access-key Minio Access Key." + echo "secrete-key Minio Secret Key." + echo +} + +if [ -z "$1" ] + then + help + exit 1; +fi + +if [ -z "$2" ] + then + help + exit 1; +fi + +echo "APP_STORAGE_ACCESS_KEY: $1" >> minio/minio_keys.env +echo "APP_STORAGE_SECRET_KEY: $2" >> minio/minio_keys.env + +cat minio/minio_keys.env + +docker compose up -d +docker compose ls +docker compose ps + +app=( postgres pgadmin developer-console-ui dco-gateway scenario-library-service tracks-management-service ) + +arraylength=${#app[@]} + +for (( i=0; i<${arraylength}; i++ )); +do + if [ $( docker ps -a | grep ${app[i]} | wc -l ) -gt 0 ]; then + printf -- '\033[32m \x1b[1m ~ ~ ~ ~ ~ '${app[i]}' is running ~ ~ ~ ~ ~ \033[0m\n\n\n' + else + printf -- '\033[31m \x1b[1m ~ ~ ~ ~ ~ '${app[i]}' container is missing ~ ~ ~ ~ ~ \033[0m\n\n\n' + fi +done + +printf -- '\033[32m \x1b[1m Below are important urls that you can use to access application, swagger and playground for REST APIs \033[0m\n\n' +printf -- '\033[32m \x1b[1m -> Developer Console UI: http://localhost:3000 \033[0m\n\n' +printf -- '\033[32m \x1b[1m -> dco-gatway playground for REST APIs: http://localhost:8080/playground \033[0m\n\n' +printf -- '\033[32m \x1b[1m -> tracks-management-service swagger for REST APIs: http://localhost:8081/openapi/swagger-ui/index.html \033[0m\n\n' +printf -- '\033[32m \x1b[1m -> scenario-library-service swagger for REST APIs: http://localhost:8082/openapi/swagger-ui/index.html \033[0m\n\n' +printf -- '\033[32m \x1b[1m -> pgadmin client for postgresql database: http://localhost:5050. The username is "admin@default.com" and password is "admin" \033[0m\n\n' +printf -- '\033[32m \x1b[1m For more details, please refer the README.md file \033[0m\n\n' \ No newline at end of file diff --git a/30-destroy-script.sh b/30-destroy-script.sh new file mode 100755 index 0000000000000000000000000000000000000000..d4e3c645dbbfbd8c87d986dd0dd2517480d91a81 --- /dev/null +++ b/30-destroy-script.sh @@ -0,0 +1,21 @@ +#!/bin/bash -e + +docker compose down --remove-orphans +cd minio +docker compose down --remove-orphans + + +app=( developer-console-ui dco-gateway scenario-library-service tracks-management-service postgres dpage/pgadmin4 minio ) +image=( developer-console-ui:1.0 dco-gateway:1.0 scenario-library-service:1.0 tracks-management-service:1.0 postgres:1.0 dpage/pgadmin4:latest minio:1.0) + +arraylength=${#app[@]} + +for (( i=0; i<${arraylength}; i++ )); +do + if [ $( docker images | grep ${app[i]} | wc -l ) -gt 0 ]; then + docker rmi ${image[i]} + printf -- '\033[32m \x1b[1m ~ ~ ~ ~ ~ Deleted '${image[i]}' image from local ~ ~ ~ ~ ~ \033[0m\n' + else + printf -- '\033[31m \x1b[1m ~ ~ ~ ~ ~ No '${image[i]}' image available on local ~ ~ ~ ~ ~ \033[0m\n' + fi +done diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000000000000000000000000000000000..4fbcbcd1b0827b70023a8e671f5232ddb2a36813 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,97 @@ +Contributing to Eclipse SDV Developer Console +====================== + +Thanks for your interest in this project. + +Project description: +-------------------- +The Eclipse- SDV Developer Console, part of T-System's Hybercube portfolio, addresses challenges faced by OEMs in software development, testing, and simulation in the automotive industry. + +Hypercube aims to accelerate time-to-market for new features by automating and standardizing software components. The Developer Console helps solve business challenges related to decreasing logistical costs, managing changing demands, and improving time, cost, and quality of tests and simulations. It provides a solution for managing simulations of software-defined vehicle components throughout the software lifecycle, including creating simulation objects, simulation setup, and execution. +eTrice The ultimate goal is to encourage the use of Hybercube and open source solutions to transform vehicles into software-defined vehicles and create new use cases and integrations for a connected world. + +Simulation is an essential tool in OTA updates for vehicles because it allows OEM to test and validate software updates in a virtual environment before deploying them to vehicles in the field. By simulating different vehicle components and systems, OEM's can identify potential issues or conflicts that may arise during the OTA update process and make modifications to ensure that the update is delivered seamlessly to the vehicle. +Some of Example of Simulations like Functional Simulation ,Performance Simulations, Security Simulations, Compatibility Simulations. + +To Create Simulation, prerequisite it to Create/use existing 1) Track/s and 2) Scenario. + +A Simulation includes : + +one or many scenario files (simulation object) in a specific order one track (simulation setup) +Thus, the simulation include the reference to the track and scenario, and request. + + + +Developer resources: +-------------------- + +Information regarding source code management, builds, coding standards, and more about SDV- Developer Console. + +- https://gitlab.eclipse.org/eclipse/dco + + The SDV DCO code is stored in a git repository. + +https://gitlab.eclipse.org/eclipse/dco +You can contribute bugfixes and new features by sending pull requests through GitHub. + +Legal: +------------------------------ +In order for your contribution to be accepted, it must comply with the Eclipse Foundation IP policy. + +Please read the Eclipse Foundation policy on accepting contributions via Git. + +1. Sign the Eclipse ECA + 1. Register for an Eclipse Foundation User ID. You can register here. + 2. Log into the Accounts Portal, and click on the 'Eclipse Contributor Agreement' link. +2. Go to your account settings and add your GitHub username to your account. +3. Make sure that you sign-off your Git commits in the following format: Signed-off-by: John Smith <johnsmith@nowhere.com> This is usually at the bottom of the commit message. You can automate this by adding the '-s' flag when you make the commits. e.g. git commit -s -m "Adding a cool feature" +4. Ensure that the email address that you make your commits with is the same one you used to sign up to the Eclipse Foundation website with. + +Before your contribution can be accepted by the project, you need to create and electronically sign the Eclipse Foundation Contributor License Agreement (CLA). + +- http://www.eclipse.org/legal/ECA.php + +Contributing a change +------------------------------ +Please refer below steps to contribute SDV DCO +1. Clone the repository onto your computer: git clone https://gitlab.eclipse.org/eclipse/dco/developer-console.git +2. Change the working directory to developer-console +3. Create a new branch from the latest master branch. +4. Please use below prefixes while creating a new branch with git checkout -b {prefix}/{your-branch-name} + a) If you are adding a new feature, then use git checkout -b feat/{your-branch-name} + b) If you are fixing some bug, then use git checkout -b fix/your-branch-name + c) If you are doing configuration/ci related changes, then use git checkout -b ci/{your-branch-name} + d) If you are doing documentation related changes, then use git checkout -b docs/{your-branch-name} +1. Ensure that all new and existing tests pass. +2. Commit the changes into the branch using below commands: + a) git add . + b) git commit -m "meaningful-commit--message" + c) git push +3. Once you push the changes to remote feature branch, raise a merge request on Gitlab to merge the code to main branch +4. Make sure that your commit message is meaningful and describes your changes correctly. +5. If you have a lot of commits for the change, squash them into a single commit. +6. Add "Santosh Kanase" user as a reviewer while raising a merge request. + +##### What happens next? +Depends on the changes done by the contributor. If it is 100% authored by the contributor and meets the needs of the project, then it become eligile for the inclusion into the main repository. If you need more specific information/details, please consult with DCO owner through mailing-list. You can refer contact section. + +Contact: +-------- + +Contact the project developers via the project's "dev" list. + +- https://accounts.eclipse.org/mailing-list/dco-dev + +Search for bugs: +---------------- + +This project uses Bugzilla to track ongoing development and issues. + +- https://bugs.eclipse.org/bugs/buglist.cgi?product=DCO + +Create a new bug: +----------------- + +Be sure to search for existing bugs before you create another one. Remember that contributions are always welcome! + +- https://bugs.eclipse.org/bugs/enter_bug.cgi?product=DCO diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000000000000000000000000000000000000..9cb2f0c8444ad1516a945ffba80f18bb1657dd91 --- /dev/null +++ b/LICENSE.md @@ -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 [2023] [T-Systems] + + 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. \ No newline at end of file diff --git a/README.md b/README.md index 84138fab16153921280ee24e7fcff65c6169df32..3c7bd7e6a5b85cdc546430a920f0799952fad409 100644 --- a/README.md +++ b/README.md @@ -1,92 +1,239 @@ -# developer-console +# Developer-Console + +## Description +SDV-Developer Console(DCO) integrates all necessary sources for software lifecycle management and thereby optimizes the complete process from development to release of software. Our core of DCO includes services for development, testing, simulations, release and lifecycle management of software. Hereby, SDV-DCOs mission is to automize as much as possible by using different concepts of re-usability and standardization by connecting different business roles along the software lifecycle on one source. As our open-source contribution, we focus on simulation of services with SDV DCO - it includes + +* Developer Console UI +* Track Management +* Scenario Library +* New Simulation + +SDV- DCO enables the user with advanced user interface to plan, prepare, execute and monitor simulations of scenarios – from vehicle & fleet behaviour to services for software defined vehicles. Everyone, can create scenarios as well as simulations of these scenarios, with integrating third-party solutions and services for instance simulators, virtual vehicle repositories or simulations analysis and monitoring tools. + +#### Develop simulation space + +###### Simulation +* Vehicle Simulations +* Simulate a vehicle for test activity +* Simulate a vehicle for non-regression +* Simulate a fleet of vehicle (e.g. with unitary behavior e.g. overload) +* Simulates basic vehicle functionalities + +###### Simulated Services: +* Remote Control: Simulate answers to remote orders +* Data collect: Simulate data collection about the car status +* OTA: Simulates the interactions with the OTA server +* Device Management: Handles the basic connectivity with backend +* xCall: e.g. simulate emergency call + +###### Future Scope: +* Test Result Analysis +* Defect Tracking +* Reporting + + + +> <span style="color:blue"> **Note:** </span> +<span style="color:blue"> **The Eclipse-SDV DCO consists of four micro-services such as DCO-UI, Track-Management-Service, Scenario-Management-Service, and Gateway-Service. These micro-services have been consolidated into the DCO-Mono-repo, named "developer-console" to simplify the deployment process for open source developers, eliminating the need for multiple deployment steps.**</span> +## Badges -## Getting started - -To make it easy for you to get started with GitLab, here's a list of recommended next steps. - -Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)! - -## Add your files +     -- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files -- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command: +    -``` -cd existing_repo -git remote add origin https://gitlab.eclipse.org/eclipse/dco/developer-console.git -git branch -M main -git push -uf origin main -``` +     -## Integrate with your tools + -- [ ] [Set up project integrations](https://gitlab.eclipse.org/eclipse/dco/developer-console/-/settings/integrations) -## Collaborate with your team +## Installation and Pre-requisites -- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/) -- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html) -- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically) -- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/) -- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html) +There are some pre-requisites. You should install java 17, spring-graphql, maven, nextjs, graphql, docker and docker compose on your local machine before getting started. -## Test and Deploy +##### Technology Stack -Use the built-in continuous integration in GitLab. +[](https://skills.thijs.gg) -- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html) -- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/) -- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html) -- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/) -- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html) +| Technology/Tools | Version | +| :------------- | :--------- | +| Java | 17 | +| Spring-Boot | 3.1.0 | +| spring-graphql | 1.2 or Above | +| Maven | 3.8 or Above | +| nextjs | 12.1.0 or Above | +| GraphQL | 16.4.0 or Above | +| Docker | 20.10.17 or Above | +| Docker Compose | v2.10.2 or Above | + +## Getting started +###### Check versions of all installed applications +Please ensure that all above softwares and listed versions are installed/available on your machine and can verify using below commands. -*** +```bash +git version +java --version +node --version +mvn --version +docker --version +docker compose version +``` +If you have all required tools and softwares installed on your machine then you are ready to start application installation. :sunglasses: +###### Clone SDV DCO repository +Clone the repository using below command. Use gitlab token if required. +```bash +# git clone +git clone https://gitlab.eclipse.org/eclipse/dco/developer-console.git + +#change working directory +cd developer-console +``` -# Editing this README +### Build and Deploy -When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template. +> <span style="color:blue"> **Note:** </span> +<span style="color:blue"> **If you do not have docker and docker compose on your machine or you are using Windows machine then below automation scripts/steps will not work as it is. In that case you have to build and install dco micro-services manually, and you have to refer below steps and screenshots to understand the configuration. Remember, Minio is a pre-requisite for scenario-library-service. Also, you will get postgres sql scripts under postgres directory.**</span> -## Suggestions for a good README -Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information. +###### Step 1: Build all micro-services and install **Minio** on local. +In this step, we are going to build all four micro-services and install Minio server as it is pre-requisite for scenario-library-service. -## Name -Choose a self-explaining name for your project. +**Note:** If you have already followed below steps, please make sure you are using latest images and executables. On sefer side, you can simply execute clean up commands before you start with build and deploy steps. -## Description -Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors. +```bash +# Change execution permission of build shell script +chmod +x 10-build-script.sh -## Badges -On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge. +# Build apps and install Minio. +sh 10-build-script.sh +``` -## Visuals -Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method. +###### Step 2: Create a new bucket named, **dco-scenario-library-service** and generate **access-key** and **secret-key** in Minio + > A) Access Minio using http://localhost:9001 url. The admin user is "**minioadmin**" and default password is "**minioadmin**" + + <br></br> + > B) Create a bucket with name "dco-scenario-library-service" + + <br></br> + > C) Generate access key and secret key. + Save both key values for future use as we need to pass both values as arguments while running deployment script. + -## Installation -Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection. +After creating and configuring the **dco-scenario-library-service** bucket, **access key** and **secret key** in Minio, run deploy script to run all dockerized micro-services on local machine using docker compose. -## Usage -Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README. +--- -## Support -Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc. +###### Step 3: Deploy - Run all micro-services including postgres database and pgadmin client for database. +```bash +# Change execution permission of deploy shell script +chmod +x 20-deploy-script.sh -## Roadmap -If you have ideas for releases in the future, it is a good idea to list them in the README. +# Execute deploy script by passing newly generated minio access key and secret key +sh 20-deploy-script.sh <access-key> <secrete-key> +``` +After successful execution of deploy step 3, you can use below urls to access web-application, playground and swagger for REST APIs. + +> 1) **Developer Console UI**: http://localhost:3000 +The username is "**developer**" and password is "**password**" + + <br></br> +> 2) **dco-gateway** playground for REST APIs: http://localhost:8080/playground +The username is "**developer**" and password is "**password**" + + <br></br> +> 3) **tracks-management-service** swagger for REST APIs: http://localhost:8081/openapi/swagger-ui/index.html +The username is "**developer**" and password is "**password**" + + <br></br> +> 4) **scenario-library-service** swagger for REST APIs: http://localhost:8082/openapi/swagger-ui/index.html +The username is "**developer**" and password is "**password**" + + <br></br> +> 5) **pgadmin** client url: http://localhost:5050 +The username is "**admin@default.com**" and password is "**admin**" + +<br></br> + a) Then click on **Add New Server** option and add server name as **local**. +  + <br></br> + b) Click on **connection** tab and provide below details. + Host name/ Address : _postgres_ + port : _5432_ + Maintenance Database: _postgres_ + Username : _postgres_ + Password : _postgres_ +  + <br></br> + c) Click on ***Save*** button. You will see below screen after successful connection. +  +--- + +### How to use SDV DCO? +The steps involved in utilizing the SDV-Developer Console (DCO), specifically the SDV-DCO, Simulation platform : + +1. Access the Developer Console UI: + - Log in to the DCO platform using the provided credentials. + - The Developer Console UI serves as the central hub for managing the Simulation platform. + +2. Create Scenarios: + - In the Scenario section of DCO, click the "New Scenario" button to create and manage various scenarios. + - These scenarios simulate different aspects of software behavior, such as vehicle and fleet behavior or services for software-defined vehicles. + - These scenarios serve as a foundation for simulations and testing. + +3. Create Track (Using Vehicle ID/Information ) + - Navigate Track Management + - Within the Developer Console UI, locate the Track section. + - This feature allows you to define and organize different tracks or projects associated with different vehicles that are part of the new scenarios. + +4. Plan New Simulations: + - While creating simulation we have to define a scenario with a track. + - Once you have created scenarios, use DCO's advanced user interface to plan “New simulations†by using New simulation button . + - Define simulation parameters, specify the scenarios to be simulated, and configure other relevant settings. + - Launch the simulations. + +5. Iterate and Refine: + - Based on the insights gained from the simulation results, iterate and refine your software development and lifecycle processes. + - Adjust scenarios, simulation parameters, and integrated services as necessary to enhance the overall software quality and performance. + +6. Monitor/View Simulation: + - During the execution of simulations, monitor the progress and gather relevant data using the Simulation view provided by DCO. + - This enables you to assess the behavior and performance of the software throughout the simulated scenarios. + +7. Prepare and Execute Simulations: - (To be Planned) + - Before executing simulations, DCO prepares the necessary resources and configurations. + - It leverages reusability and standardization concepts to optimize the process and reduce manual effort. + - DCO allows integration of third-party solutions and services, such as simulators, virtual vehicle repositories, or simulation analysis and monitoring tools. + +8. Analyze and Evaluate Results: - (To be Planned) + - Once the simulations are complete, analyze the gathered data and evaluate the results. + - This analysis helps in understanding the impact of different scenarios on the software and identifying areas for improvement or optimization. + + + +By following these steps, you can leverage the SDV-Developer Console (DCO) platform, specifically the Simulation activities for multiple scenarios involving vehicles. + +--- +### Clean Up: +Once you are done with the exploring of SDV DCO, you can use below steps to simply delete all configured and installed services and tools from your machine. +###### Remove all micro-services, Minio, postgres database and pgadmin client from local machine. +```bash +# Change execution permission of destroy shell script +chmod +x 30-destroy-script.sh + +# To remove all apps. +sh 30-destroy-script.sh +``` ## Contributing -State if you are open to contributions and what your requirements are for accepting them. +For people who want to make changes to this project, please refer [CONTRIBUTING.md](CONTRIBUTING.md) file for details. -For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self. +## License +For license details, please refer [LICENSE.md](LICENSE.md) file -You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser. +## Architecture +SDV DCO Architecture -## Authors and acknowledgment -Show your appreciation to those who have contributed to the project. + -## License -For open source projects, say how it is licensed. -## Project status -If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers. +© T-Systems diff --git a/dco-gateway/.gitignore b/dco-gateway/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..3b5c582a3c6f2179885bbc6ba8f0273acee895c8 --- /dev/null +++ b/dco-gateway/.gitignore @@ -0,0 +1,24 @@ +HELP.md +target/ +app/target +api/target +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + + diff --git a/dco-gateway/Dockerfile.app b/dco-gateway/Dockerfile.app new file mode 100644 index 0000000000000000000000000000000000000000..f53cc2d1c71d46665e3103dda2482b4e010502cc --- /dev/null +++ b/dco-gateway/Dockerfile.app @@ -0,0 +1,4 @@ +FROM amazoncorretto:17-al2-jdk AS app +COPY dco-gateway/app/target/*.jar /app/app.jar +WORKDIR /app +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/dco-gateway/api/openapi/openapi-scenario.yml b/dco-gateway/api/openapi/openapi-scenario.yml new file mode 100644 index 0000000000000000000000000000000000000000..8dd69ac74561a95071473aee86cd0d97bd881fc9 --- /dev/null +++ b/dco-gateway/api/openapi/openapi-scenario.yml @@ -0,0 +1,464 @@ +# TODO: adapt openapi specification itself and file name +openapi: 3.0.1 +info: + title: openapi-scenario + version: latest +servers: + - url: http://localhost:8080 +tags: + - name: Scenario + description: The endpoints for scenario interactions + - name: Simulation + description: The endpoints for simulation interactions +paths: + /api/scenario: + post: + tags: + - Scenario + summary: Create a Scenario + description: Create a Scenario to database + operationId: createScenario + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + scenario: + type: string + file: + type: string + format: binary + responses: + "201": + description: Created + content: + application/json: + schema: + $ref: "#/components/schemas/Scenario" + "400": + description: Bad Request + get: + tags: + - Scenario + summary: Read Scenario by query + description: Read Scenario by query and pageable from database + operationId: scenarioReadByQuery + parameters: + - name: query + in: query + required: false + description: >- + Comma separated list of `{field}{operation}{value}` where operation can be + `:` for equal, + `!` for not equal and + `~` for like operation + schema: + type: string + - name: search + in: query + required: false + description: Search value to query searchable fields against + schema: + type: string + - name: page + in: query + required: false + example: 0 + schema: + type: integer + format: int32 + - name: size + in: query + required: false + example: 15 + schema: + type: integer + format: int32 + - name: sort + in: query + required: false + example: + - name:asc + schema: + type: array + items: + type: string + example: name:asc + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/ScenarioPage" + "400": + description: Bad Request + put: + tags: + - Scenario + summary: Update Scenario by id + description: Update Scenario by id to database + operationId: scenarioUpdateById + parameters: + - name: id + in: query + description: The scenario id + required: true + schema: + type: string + format: uuid + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + scenario: + type: string + file: + type: string + format: binary + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/Scenario" + "400": + description: Bad Request + "404": + description: Not Found + delete: + tags: + - Scenario + summary: Delete Scenario by id + description: Delete Scenario by id from database + operationId: deleteScenarioById + parameters: + - name: id + in: query + description: The scenario id + required: true + schema: + type: string + format: uuid + responses: + "204": + description: No Content + "404": + description: Not Found + /api/scenario/search: + get: + tags: + - Scenario + summary: Search for scenario with given '%' pattern. Returns paginated list + description: Search for scenario with given '%' pattern. Returns paginated list + operationId: searchScenarioByPattern + parameters: + - name: scenarioPattern + in: query + required: true + schema: + type: string + - name: page + in: query + required: false + example: 0 + schema: + type: integer + format: int32 + - name: size + in: query + required: false + example: 15 + schema: + type: integer + format: int32 + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/ScenarioPage" + "404": + description: Not Found + /api/simulation: + post: + tags: + - Simulation + summary: Launch Simulation + description: Launch a Simulation + operationId: launchSimulation + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/SimulationInput" + required: true + responses: + "201": + description: Created + content: + application/json: + schema: + type: string + "400": + description: Bad Request + "404": + description: Not Found + get: + tags: + - Simulation + summary: Read Simulation by query + description: Read Simulation by query and pageable from database + operationId: simulationReadByQuery + parameters: + - name: query + in: query + required: false + description: >- + Comma separated list of `{field}{operation}{value}` where operation can be + `:` for equal, + `!` for not equal and + `~` for like operation + schema: + type: string + - name: search + in: query + required: false + description: Search value to query searchable fields against + schema: + type: string + - name: page + in: query + required: false + example: 0 + schema: + type: integer + format: int32 + - name: size + in: query + required: false + example: 15 + schema: + type: integer + format: int32 + - name: sort + in: query + required: false + example: + - name:asc + schema: + type: array + items: + type: string + example: name:asc + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/SimulationPage" + "400": + description: Bad Request +components: + schemas: + ScenarioInput: + type: object + properties: + name: + type: string + status: + type: string + enum: [ CREATED, ARCHIVED ] + type: + type: string + enum: [ MQTT, CAN ] + description: + type: string + createdBy: + type: string + lastModifiedBy: + type: string + description: The scenario data + FileData: + type: object + properties: + id: + type: string + format: uuid + path: + type: string + fileKey: + type: string + size: + type: string + checksum: + type: string + updatedBy: + type: string + updatedOn: + type: string + format: date-time + description: The scenario data + ScenarioPage: + type: object + properties: + content: + type: array + items: + $ref: '#/components/schemas/Scenario' + empty: + type: boolean + first: + type: boolean + last: + type: boolean + page: + type: integer + format: int32 + size: + type: integer + format: int32 + pages: + type: integer + format: int32 + elements: + type: integer + format: int32 + total: + type: integer + format: int64 + description: The Scenario page data + Scenario: + type: object + properties: + id: + type: string + format: uuid + name: + type: string + type: + type: string + status: + type: string + description: + type: string + createdAt: + type: string + format: date-time + createdBy: + type: string + lastModifiedAt: + type: string + format: date-time + lastModifiedBy: + type: string + file: + $ref: "#/components/schemas/FileData" + description: The scenario data + SimulationInput: + type: object + properties: + name: + type: string + environment: + type: string + platform: + type: string + scenarioType: + type: string + hardware: + type: string + description: + type: string + tracks: + type: array + items: + type: string + format: uuid + scenarios: + type: array + items: + type: string + format: uuid + createdBy: + type: string + description: launch simulation input + SimulationPage: + type: object + properties: + content: + type: array + items: + $ref: '#/components/schemas/Simulation' + empty: + type: boolean + first: + type: boolean + last: + type: boolean + page: + type: integer + format: int32 + size: + type: integer + format: int32 + pages: + type: integer + format: int32 + elements: + type: integer + format: int32 + total: + type: integer + format: int64 + description: The Scenario page data + Simulation: + type: object + properties: + id: + type: string + format: uuid + name: + type: string + status: + type: string + platform: + type: string + hardware: + type: string + environment: + type: string + scenarioType: + type: enum + $ref: "#/components/schemas/ScenarioType" + noOfScenarios: + type: integer + format: int32 + noOfVehicle: + type: integer + format: int32 + brands: + type: array + items: + type: string + createdBy: + type: string + startDate: + type: string + format: date-time + description: + type: string + description: Simulation Data + ScenarioType: + type: string + enum: + - Over-The-Air Service + - Vehicle Management + - Data Collection + - Remote Control diff --git a/dco-gateway/api/openapi/openapi-track.yml b/dco-gateway/api/openapi/openapi-track.yml new file mode 100644 index 0000000000000000000000000000000000000000..fa79cfd68f574ee29a75759281a716e5f12effcd --- /dev/null +++ b/dco-gateway/api/openapi/openapi-track.yml @@ -0,0 +1,490 @@ +# TODO: adapt openapi specification itself and file name +openapi: 3.0.1 +info: + title: openapi-track + version: latest +servers: + - url: http://localhost:8080 +tags: + - name: Track + description: The endpoints for track interactions + - name: Vehicle + description: The endpoints for vehicle interactions +paths: + /api/track: + post: + tags: + - Track + summary: Create a Track + description: Create a Track to database + operationId: createTrack + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/TrackInput" + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/Track" + "400": + description: Bad Request + "404": + description: Not Found + get: + tags: + - Track + summary: Read Track by query + description: Read Track by query and pageable from database + operationId: trackReadByQuery + parameters: + - name: query + in: query + required: false + example: brand:vw,brand!bmw,brand~benz + description: >- + Comma separated list of `{field}{operation}{value}` where operation can be + `:` for equal, + `!` for not equal and + `~` for like operation + schema: + type: string + - name: search + in: query + required: false + description: Search value to query searchable fields agains + schema: + type: string + - name: page + in: query + required: false + example: 0 + schema: + type: integer + format: int32 + - name: size + in: query + required: false + example: 15 + schema: + type: integer + format: int32 + - name: sort + in: query + required: false + example: + - name:asc + schema: + type: array + items: + type: string + example: name:asc + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/TrackPage" + "404": + description: Not Found + delete: + tags: + - Track + summary: Delete a track by id + description: Delete a track by id from database + operationId: deleteTrackById + parameters: + - name: id + in: path + description: The track id + required: true + schema: + type: string + format: uuid + responses: + "200": + description: OK + content: + application/json: + schema: + type: string + "400": + description: Bad Request + "404": + description: Not Found + /api/track/{id}: + get: + tags: + - Track + summary: Find track by id + description: Find track by id + operationId: findTrackById + parameters: + - name: id + in: path + description: The track id + required: true + schema: + type: string + format: uuid + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/Track" + "404": + description: Not Found + /api/track/search: + get: + tags: + - Track + summary: Search for tracks with given '%' pattern. Returns paginated list + description: Search for tracks with given '%' pattern. Returns paginated list + operationId: searchTrackByPattern + parameters: + - name: trackPattern + in: query + required: true + schema: + type: string + - name: page + in: query + required: false + example: 0 + schema: + type: integer + format: int32 + - name: size + in: query + required: false + example: 15 + schema: + type: integer + format: int32 + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/TrackPage" + "404": + description: Not Found + /api/vehicle/{vin}: + get: + tags: + - Vehicle + summary: Find vehicle by vin + description: Find vehicle by vin + operationId: getVehicleByVin + parameters: + - name: vin + in: path + description: The vehicle vin + required: true + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/VehicleResponse" + "404": + description: Not Found + /api/track/hardware: + get: + tags: + - Track + summary: Read Hardware Module + description: Read Hardware Module + operationId: getHardwareModule + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + type: String + "404": + description: Not Found + /api/vehicle: + get: + tags: + - Vehicle + summary: Read vehicle by query + description: Read Vehicle by query and pageable from device service + operationId: vehicleReadByQuery + parameters: + - name: query + in: query + required: false + description: >- + Comma separated list of `{field}{operation}{value}` where operation can be + `:` for equal, + `!` for not equal and + `~` for like operation + schema: + type: string + - name: search + in: query + required: false + description: Search value to query searchable fields against + schema: + type: string + - name: page + in: query + required: false + example: 0 + schema: + type: integer + format: int32 + - name: size + in: query + required: false + example: 15 + schema: + type: integer + format: int32 + - name: sort + in: query + required: false + example: + - name:asc + schema: + type: array + items: + type: string + example: name:asc + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/VehiclePage" + "404": + description: Not Found +components: + schemas: + TrackPage: + type: object + properties: + content: + type: array + items: + $ref: '#/components/schemas/Track' + empty: + type: boolean + first: + type: boolean + last: + type: boolean + page: + type: integer + format: int32 + size: + type: integer + format: int32 + pages: + type: integer + format: int32 + elements: + type: integer + format: int32 + total: + type: integer + format: int64 + description: The Track page data + Track: + type: object + properties: + id: + type: string + format: uuid + name: + type: string + trackType: + type: string + state: + type: string + duration: + type: string + description: + type: string + vehicles: + type: array + items: + $ref: "#/components/schemas/VehicleResponse" + description: Track data + VehicleResponse: + type: object + properties: + vin: + type: string + owner: + type: string + ecomDate: + type: string + country: + type: string + model: + type: string + brand: + type: string + region: + type: string + createdAt: + type: string + instantiatedAt: + type: string + updatedAt: + type: string + status: + type: string + type: + type: string + fleets: + type: array + items: + $ref: "#/components/schemas/FleetResponse" + devices: + type: array + items: + $ref: "#/components/schemas/DeviceResponse" + services: + type: array + items: + $ref: "#/components/schemas/ServiceResponse" + tags: + type: array + items: + type: string + description: vehicle data + FleetResponse: + type: object + properties: + id: + type: string + name: + type: string + type: + type: string + description: Fleet data + ServiceResponse: + type: object + properties: + serviceId: + type: string + operation: + type: string + updatedAt: + type: string + description: Fleet data + DeviceResponse: + type: object + properties: + id: + type: string + type: + type: string + status: + type: string + createdAt: + type: string + gatewayId: + type: string + modelType: + type: string + dmProtocol: + type: string + modifiedAt: + type: string + dmProtocolVersion: + type: string + serialNumber: + type: string + components: + type: array + items: + $ref: "#/components/schemas/DeviceComponentResponse" + description: device data + DeviceComponentResponse: + type: object + properties: + id: + type: string + name: + type: string + status: + type: string + version: + type: string + environmentType: + type: string + description: device component data + TrackInput: + type: object + properties: + name: + type: string + trackType: + type: string + state: + type: string + duration: + type: string + description: + type: string + vehicles: + type: array + items: + $ref: "#/components/schemas/Vehicle" + description: create track request data + Vehicle: + type: object + properties: + vin: + type: string + country: + type: string + description: vehicle data for track creation + VehiclePage: + type: object + properties: + content: + type: array + items: + $ref: '#/components/schemas/VehicleResponse' + empty: + type: boolean + first: + type: boolean + last: + type: boolean + page: + type: integer + format: int32 + size: + type: integer + format: int32 + pages: + type: integer + format: int32 + elements: + type: integer + format: int32 + total: + type: integer + format: int64 + description: The Vehicle page data diff --git a/dco-gateway/api/pom.xml b/dco-gateway/api/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..322e4ab83192d1b7abe881df74e8f6b23d1fcdac --- /dev/null +++ b/dco-gateway/api/pom.xml @@ -0,0 +1,109 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <artifactId>dco-gateway</artifactId> + <groupId>com.tsystems.dco</groupId> + <version>latest</version> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>dco-gateway-api</artifactId> + <packaging>jar</packaging> + + <properties> + <cyclonedx.skip>true</cyclonedx.skip> + <dependency-track.skip>true</dependency-track.skip> + </properties> + + <dependencies> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-web</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.springdoc</groupId> + <artifactId>springdoc-openapi-ui</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.openapitools</groupId> + <artifactId>openapi-generator-maven-plugin</artifactId> + <executions> + <execution> + <id>openapi-scenario</id> + <goals> + <goal>generate</goal> + </goals> + <configuration> + <inputSpec>${project.basedir}/openapi/openapi-scenario.yml</inputSpec> + <generatorName>spring</generatorName> + <packageName>com.tsystems.dco.scenario</packageName> + <invokerPackage>com.tsystems.dco.scenario</invokerPackage> + <apiPackage>com.tsystems.dco.scenario.api</apiPackage> + <modelPackage>com.tsystems.dco.scenario.model</modelPackage> + <generateSupportingFiles>false</generateSupportingFiles> + <typeMappings> + <typeMapping>OffsetDateTime=Instant</typeMapping> + </typeMappings> + <importMappings> + <importMapping>java.time.OffsetDateTime=java.time.Instant</importMapping> + </importMappings> + <configOptions> + <useTags>true</useTags> + <useSpringBoot3>true</useSpringBoot3> + <interfaceOnly>true</interfaceOnly> + <serializableModel>true</serializableModel> + <skipDefaultInterface>true</skipDefaultInterface> + <hideGenerationTimestamp>true</hideGenerationTimestamp> + <openApiNullable>false</openApiNullable> + <additionalModelTypeAnnotations>@lombok.Builder @lombok.NoArgsConstructor @lombok.AllArgsConstructor</additionalModelTypeAnnotations> + </configOptions> + </configuration> + </execution> + <execution> + <id>openapi-track</id> + <goals> + <goal>generate</goal> + </goals> + <configuration> + <inputSpec>${project.basedir}/openapi/openapi-track.yml</inputSpec> + <generatorName>spring</generatorName> + <packageName>com.tsystems.dco.track</packageName> + <invokerPackage>com.tsystems.dco.track</invokerPackage> + <apiPackage>com.tsystems.dco.track.api</apiPackage> + <modelPackage>com.tsystems.dco.track.model</modelPackage> + <generateSupportingFiles>false</generateSupportingFiles> + <typeMappings> + <typeMapping>OffsetDateTime=Instant</typeMapping> + </typeMappings> + <importMappings> + <importMapping>java.time.OffsetDateTime=java.time.Instant</importMapping> + </importMappings> + <configOptions> + <useTags>true</useTags> + <useSpringBoot3>true</useSpringBoot3> + <interfaceOnly>true</interfaceOnly> + <serializableModel>true</serializableModel> + <skipDefaultInterface>true</skipDefaultInterface> + <hideGenerationTimestamp>true</hideGenerationTimestamp> + <openApiNullable>false</openApiNullable> + <additionalModelTypeAnnotations>@lombok.Builder @lombok.NoArgsConstructor @lombok.AllArgsConstructor</additionalModelTypeAnnotations> + </configOptions> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project> diff --git a/dco-gateway/app/pom.xml b/dco-gateway/app/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..4a44b11fd60b55b4b622fe4057ef87ac936c5e75 --- /dev/null +++ b/dco-gateway/app/pom.xml @@ -0,0 +1,161 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <artifactId>dco-gateway</artifactId> + <groupId>com.tsystems.dco</groupId> + <version>latest</version> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>dco-gateway-app</artifactId> + <properties> + <cyclonedx.skip>true</cyclonedx.skip> + <dependency-track.skip>true</dependency-track.skip> + <sonar.coverage.exclusions> + **/App.java, + **/src/main/java/com/tsystems/dco/config/**, + **/src/main/java/com/tsystems/dco/AppProperties** + </sonar.coverage.exclusions> + </properties> + + <dependencies> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>dco-gateway-api</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-actuator</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-validation</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-web</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-configuration-processor</artifactId> + <optional>true</optional> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-graphql</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-starter-openfeign</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-security</artifactId> + </dependency> + <dependency> + <groupId>com.graphql-java-kickstart</groupId> + <artifactId>graphql-java-tools</artifactId> + <version>13.0.3</version> + </dependency> + <dependency> + <groupId>com.graphql-java-kickstart</groupId> + <artifactId>graphql-spring-boot-starter</artifactId> + <version>15.0.0</version> + </dependency> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + </dependency> + <dependency> + <groupId>org.mapstruct</groupId> + <artifactId>mapstruct</artifactId> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.module</groupId> + <artifactId>jackson-module-jaxb-annotations</artifactId> + </dependency> + <dependency> + <groupId>org.json</groupId> + <artifactId>json</artifactId> + <version>20230227</version> + </dependency> + <!-- test --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-test</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.springframework.graphql</groupId> + <artifactId>spring-graphql-test</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <resources> + <resource> + <directory>src/main/resources</directory> + <filtering>true</filtering> + </resource> + </resources> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <annotationProcessorPaths> + <path> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + <version>1.18.24</version> + </path> + <path> + <groupId>org.mapstruct</groupId> + <artifactId>mapstruct-processor</artifactId> + <version>1.4.2.Final</version> + </path> + <path> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-configuration-processor</artifactId> + <version>2.7.4</version> + </path> + </annotationProcessorPaths> + </configuration> + </plugin> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + <executions> + <execution> + <id>prepare-agent</id> + <goals> + <goal>prepare-agent</goal> + </goals> + </execution> + <execution> + <id>report</id> + <phase>verify</phase> + <goals> + <goal>report</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + <executions> + <execution> + <goals> + <goal>repackage</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project> diff --git a/dco-gateway/app/src/main/java/com/tsystems/dco/App.java b/dco-gateway/app/src/main/java/com/tsystems/dco/App.java new file mode 100644 index 0000000000000000000000000000000000000000..ca6bf608c2ed0225c5fac55336201ad9c2c653e1 --- /dev/null +++ b/dco-gateway/app/src/main/java/com/tsystems/dco/App.java @@ -0,0 +1,47 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.openfeign.EnableFeignClients; + +/** + * Entrypoint of application. + */ +@SpringBootApplication +@EnableFeignClients +@EnableConfigurationProperties +public class App { + + /** + * Main application entrypoint. + * + * @param args command line arguments + */ + public static void main(String[] args) { + SpringApplication.run(App.class, args); + } +} diff --git a/dco-gateway/app/src/main/java/com/tsystems/dco/AppProperties.java b/dco-gateway/app/src/main/java/com/tsystems/dco/AppProperties.java new file mode 100644 index 0000000000000000000000000000000000000000..ac1ea31428c3568b689d367d65aafe8bea43f7d8 --- /dev/null +++ b/dco-gateway/app/src/main/java/com/tsystems/dco/AppProperties.java @@ -0,0 +1,105 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; +import org.springframework.context.annotation.Configuration; + +/** + * Properties of application. + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Configuration +@ConfigurationProperties(prefix = "app") +public class AppProperties { + + /** + * The app name. + */ + private String name; + /** + * The app version. + */ + private String version; + /** + * The rest properties. + */ + @NestedConfigurationProperty + private Rest rest; + /** + * The probes properties. + */ + @NestedConfigurationProperty + private Probes probes; + /** + * The cors properties. + */ + @NestedConfigurationProperty + private Cors cors; + + /** + * Properties of rest. + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Rest { + + private Integer port; + } + + /** + * Properties of probes. + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Probes { + + private Integer port; + } + + /** + * Properties of cors. + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Cors { + + private String headers; + private String origins; + } +} diff --git a/dco-gateway/app/src/main/java/com/tsystems/dco/config/GraphQLConfiguration.java b/dco-gateway/app/src/main/java/com/tsystems/dco/config/GraphQLConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..9d76caa2e0cd24a41ca5c90f3306c7d3c5b35627 --- /dev/null +++ b/dco-gateway/app/src/main/java/com/tsystems/dco/config/GraphQLConfiguration.java @@ -0,0 +1,342 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.config; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.tsystems.dco.exception.BaseException; +import graphql.schema.*; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.boot.autoconfigure.graphql.GraphQlProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.core.annotation.Order; +import org.springframework.graphql.ExecutionGraphQlRequest; +import org.springframework.graphql.execution.RuntimeWiringConfigurer; +import org.springframework.graphql.server.WebGraphQlHandler; +import org.springframework.graphql.server.WebGraphQlRequest; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.lang.Nullable; +import org.springframework.util.AlternativeJdkIdGenerator; +import org.springframework.util.Assert; +import org.springframework.util.IdGenerator; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.multipart.support.AbstractMultipartHttpServletRequest; +import org.springframework.web.server.ServerWebInputException; +import org.springframework.web.servlet.function.*; +import reactor.core.publisher.Mono; + +import java.net.URI; +import java.util.*; +import java.util.regex.Pattern; + +import static org.springframework.http.MediaType.MULTIPART_FORM_DATA; + +@Configuration +public class GraphQLConfiguration { + + @Bean + public RuntimeWiringConfigurer runtimeWiringConfigurerUpload() { + + GraphQLScalarType uploadScalar = GraphQLScalarType.newScalar() + .name("Upload") + .coercing(new UploadCoercing()) + .build(); + + return wiringBuilder -> wiringBuilder.scalar(uploadScalar); + } + + @Bean + @Order(1) + public RouterFunction<ServerResponse> graphQlMultipartRouterFunction( + GraphQlProperties properties, + WebGraphQlHandler webGraphQlHandler, + ObjectMapper objectMapper + ) { + String path = properties.getPath(); + var builder = RouterFunctions.route(); + var graphqlMultipartHandler = new GraphqlMultipartHandler(webGraphQlHandler, objectMapper); + builder = builder.POST(path, RequestPredicates.contentType(MULTIPART_FORM_DATA) + .and(RequestPredicates.accept(MediaType.MULTIPART_FORM_DATA)), graphqlMultipartHandler::handleRequest); + return builder.build(); + } +} + +class UploadCoercing implements Coercing<MultipartFile, MultipartFile> { + + @Override + public MultipartFile serialize(Object dataFetcherResult) throws CoercingSerializeException { + throw new CoercingSerializeException("Upload is an input-only type"); + } + + @Override + public MultipartFile parseValue(Object input) throws CoercingParseValueException { + if (input instanceof MultipartFile) { + return (MultipartFile) input; + } + throw new CoercingParseValueException( + String.format("Expected a 'MultipartFile' like object but was '%s'.", input != null ? input.getClass() : null) + ); + } + + @Override + public MultipartFile parseLiteral(Object input) throws CoercingParseLiteralException { + throw new CoercingParseLiteralException("Parsing literal of 'MultipartFile' is not supported"); + } +} + +class GraphqlMultipartHandler { + + private final WebGraphQlHandler graphQlHandler; + + private final ObjectMapper objectMapper; + + public GraphqlMultipartHandler(WebGraphQlHandler graphQlHandler, ObjectMapper objectMapper) { + Assert.notNull(graphQlHandler, "WebGraphQlHandler is required"); + Assert.notNull(objectMapper, "ObjectMapper is required"); + this.graphQlHandler = graphQlHandler; + this.objectMapper = objectMapper; + } + + protected static final List<MediaType> SUPPORTED_RESPONSE_MEDIA_TYPES = + Arrays.asList(MediaType.APPLICATION_GRAPHQL, MediaType.APPLICATION_JSON); + + private static final Log logger = LogFactory.getLog(GraphqlMultipartHandler.class); + private static final String VARIABLES = "variables"; + + private final IdGenerator idGenerator = new AlternativeJdkIdGenerator(); + + public ServerResponse handleRequest(ServerRequest serverRequest) { + Optional<String> operation = serverRequest.param("operations"); + Optional<String> mapParam = serverRequest.param("map"); + Map<String, Object> inputQuery = readJson(operation, new TypeReference<>() { + }); + final Map<String, Object> queryVariables; + if (inputQuery.containsKey(VARIABLES)) { + queryVariables = (Map<String, Object>) inputQuery.get(VARIABLES); + } else { + queryVariables = new HashMap<>(); + } + + Map<String, MultipartFile> fileParams = readMultipartBody(serverRequest); + Map<String, List<String>> fileMapInput = readJson(mapParam, new TypeReference<>() { + }); + fileMapInput.forEach((String fileKey, List<String> objectPaths) -> { + MultipartFile file = fileParams.get(fileKey); + if (file != null) { + objectPaths.forEach((String objectPath) -> + MultipartVariableMapper.mapVariable( + objectPath, + queryVariables, + file + ) + ); + } + }); + + String query = (String) inputQuery.get("query"); + String opName = (String) inputQuery.get("operationName"); + + WebGraphQlRequest graphQlRequest = new MultipartGraphQlRequest( + query, + opName, + queryVariables, + serverRequest.uri(), serverRequest.headers().asHttpHeaders(), + this.idGenerator.generateId().toString(), LocaleContextHolder.getLocale()); + + if (logger.isDebugEnabled()) { + logger.debug("Executing: " + graphQlRequest); + } + + Mono<ServerResponse> responseMono = this.graphQlHandler.handleRequest(graphQlRequest) + .map(response -> { + if (logger.isDebugEnabled()) { + logger.debug("Execution complete"); + } + ServerResponse.BodyBuilder builder = ServerResponse.ok(); + builder.headers(headers -> headers.putAll(response.getResponseHeaders())); + builder.contentType(selectResponseMediaType(serverRequest)); + return builder.body(response.toMap()); + }); + + return ServerResponse.async(responseMono); + } + + private <T> T readJson(Optional<String> string, TypeReference<T> t) { + Map<String, Object> map = new HashMap<>(); + if (string.isPresent()) { + try { + return objectMapper.readValue(string.get(), t); + } catch (JsonProcessingException e) { + throw new BaseException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); + } + } + return (T) map; + } + + private static Map<String, MultipartFile> readMultipartBody(ServerRequest request) { + try { + var abstractMultipartHttpServletRequest = (AbstractMultipartHttpServletRequest) request.servletRequest(); + return abstractMultipartHttpServletRequest.getFileMap(); + } catch (RuntimeException ex) { + throw new ServerWebInputException("Error while reading request parts", null, ex); + } + } + + private static MediaType selectResponseMediaType(ServerRequest serverRequest) { + for (MediaType accepted : serverRequest.headers().accept()) { + if (SUPPORTED_RESPONSE_MEDIA_TYPES.contains(accepted)) { + return accepted; + } + } + return MediaType.APPLICATION_JSON; + } + +} + +class MultipartVariableMapper { + + private static final Pattern PERIOD = Pattern.compile("\\."); + private static final String VARIABLES = "variables"; + + private static final Mapper<Map<String, Object>> MAP_MAPPER = + new Mapper<Map<String, Object>>() { + @Override + public Object set(Map<String, Object> location, String target, MultipartFile value) { + return location.put(target, value); + } + + @Override + public Object recurse(Map<String, Object> location, String target) { + return location.get(target); + } + }; + private static final Mapper<List<Object>> LIST_MAPPER = + new Mapper<List<Object>>() { + @Override + public Object set(List<Object> location, String target, MultipartFile value) { + return location.set(Integer.parseInt(target), value); + } + + @Override + public Object recurse(List<Object> location, String target) { + return location.get(Integer.parseInt(target)); + } + }; + + @SuppressWarnings({"unchecked", "rawtypes"}) + public static void mapVariable(String objectPath, Map<String, Object> variables, MultipartFile part) { + String[] segments = PERIOD.split(objectPath); + + if (segments.length < 2) { + throw new BaseException(HttpStatus.INTERNAL_SERVER_ERROR, "object-path in map must have at least two segments"); + } else if (!VARIABLES.equals(segments[0])) { + throw new BaseException(HttpStatus.INTERNAL_SERVER_ERROR, "can only map into variables"); + } + + Object currentLocation = variables; + for (var i = 1; i < segments.length; i++) { + String segmentName = segments[i]; + Mapper mapper = determineMapper(currentLocation, objectPath, segmentName); + + if (i == segments.length - 1) { + if (null != mapper.set(currentLocation, segmentName, part)) { + throw new BaseException(HttpStatus.INTERNAL_SERVER_ERROR, "expected null value when mapping " + objectPath); + } + } else { + currentLocation = mapper.recurse(currentLocation, segmentName); + if (null == currentLocation) { + throw new BaseException(HttpStatus.INTERNAL_SERVER_ERROR, "found null intermediate value when trying to map " + objectPath); + } + } + } + } + + private static Mapper<?> determineMapper( + Object currentLocation, String objectPath, String segmentName) { + if (currentLocation instanceof Map) { + return MAP_MAPPER; + } else if (currentLocation instanceof List) { + return LIST_MAPPER; + } + + throw new BaseException(HttpStatus.INTERNAL_SERVER_ERROR, "expected a map or list at " + segmentName + " when trying to map " + objectPath); + } + + interface Mapper<T> { + + Object set(T location, String target, MultipartFile value); + + Object recurse(T location, String target); + } +} + +class MultipartGraphQlRequest extends WebGraphQlRequest implements ExecutionGraphQlRequest { + + private final String document; + private final String operationName; + private final Map<String, Object> variables; + + + public MultipartGraphQlRequest( + String query, + String operationName, + Map<String, Object> variables, + URI uri, HttpHeaders headers, + String id, @Nullable Locale locale) { + + super(uri, headers, fakeBody(query), id, locale); + + this.document = query; + this.operationName = operationName; + this.variables = variables; + } + + private static Map<String, Object> fakeBody(String query) { + Map<String, Object> fakeBody = new HashMap<>(); + fakeBody.put("query", query); + return fakeBody; + } + + @Override + public String getDocument() { + return document; + } + + @Override + public String getOperationName() { + return operationName; + } + + @Override + public Map<String, Object> getVariables() { + return variables; + } + +} diff --git a/dco-gateway/app/src/main/java/com/tsystems/dco/config/SecurityConfig.java b/dco-gateway/app/src/main/java/com/tsystems/dco/config/SecurityConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..9039680800bc3020f790fd86e7eb5c52e6b3bbb8 --- /dev/null +++ b/dco-gateway/app/src/main/java/com/tsystems/dco/config/SecurityConfig.java @@ -0,0 +1,56 @@ +package com.tsystems.dco.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.web.SecurityFilterChain; + +import static org.springframework.security.config.Customizer.withDefaults; + +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + @Value("${app.username}") + private String username; + @Value("${app.password}") + private String password; + + @Bean + public SecurityFilterChain configure(HttpSecurity http) throws Exception { + return http.cors().and().csrf().disable() + .authorizeRequests(auth -> auth.anyRequest().authenticated()) + .httpBasic(withDefaults()) + .build(); + } + + @Bean + public InMemoryUserDetailsManager userDetailsManager() { + + UserDetails user1 = User.withDefaultPasswordEncoder() + .username(username) + .password(password) + .roles("USER") + .build(); + + UserDetails user2 = User.withDefaultPasswordEncoder() + .username("dco") + .password("dco") + .roles("USER") + .build(); + + UserDetails admin = User.withDefaultPasswordEncoder() + .username("admin") + .password("password") + .roles("USER","ADMIN") + .build(); + + return new InMemoryUserDetailsManager(user1, user2, admin); + } + +} diff --git a/dco-gateway/app/src/main/java/com/tsystems/dco/config/WebConfig.java b/dco-gateway/app/src/main/java/com/tsystems/dco/config/WebConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..f1a8e9ad383b6ddbb926fdaacd1f12db93ee41c2 --- /dev/null +++ b/dco-gateway/app/src/main/java/com/tsystems/dco/config/WebConfig.java @@ -0,0 +1,48 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.config; + +import com.tsystems.dco.AppProperties; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * Configuration for MVC and cors. + */ +@Configuration +@RequiredArgsConstructor +public class WebConfig implements WebMvcConfigurer { + + private final AppProperties properties; + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedMethods("HEAD", "GET", "PUT", "POST", "DELETE", "PATCH", "OPTIONS") + .allowedOrigins(properties.getCors().getOrigins()) + .allowedHeaders(properties.getCors().getHeaders()); + } +} diff --git a/dco-gateway/app/src/main/java/com/tsystems/dco/exception/BaseException.java b/dco-gateway/app/src/main/java/com/tsystems/dco/exception/BaseException.java new file mode 100644 index 0000000000000000000000000000000000000000..ca35ab36f5789022f46802442c99602763dae312 --- /dev/null +++ b/dco-gateway/app/src/main/java/com/tsystems/dco/exception/BaseException.java @@ -0,0 +1,49 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.exception; + +import lombok.Getter; +import lombok.ToString; +import org.springframework.http.HttpStatus; + +/** + * Base exception that contains a http status code + */ +@Getter +@ToString +public class BaseException extends RuntimeException { + + private final HttpStatus status; + + /** + * Base exception constructor with status and message. + * + * @param status The http status that should occur when exception is thrown + * @param message The message that should occur when exception is thrown + */ + public BaseException(HttpStatus status, String message) { + super(message); + this.status = status; + } +} diff --git a/dco-gateway/app/src/main/java/com/tsystems/dco/exception/CustomExceptionHandler.java b/dco-gateway/app/src/main/java/com/tsystems/dco/exception/CustomExceptionHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..bf8a664b6136c351ef2f82e4440b77f0715908bf --- /dev/null +++ b/dco-gateway/app/src/main/java/com/tsystems/dco/exception/CustomExceptionHandler.java @@ -0,0 +1,81 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.exception; + +import graphql.GraphQLError; +import graphql.GraphqlErrorBuilder; +import graphql.schema.DataFetchingEnvironment; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.json.JSONObject; +import org.springframework.graphql.execution.DataFetcherExceptionResolverAdapter; +import org.springframework.graphql.execution.ErrorType; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class CustomExceptionHandler extends DataFetcherExceptionResolverAdapter { + private static final String KEY_STATUS = "status"; + private static final String KEY_MESSAGE = "message"; + + /** + * @param ex the exception to resolve + * @param env the environment for the invoked {@code DataFetcher} + * @return GraphQLError + */ + @SneakyThrows + @Override + public GraphQLError resolveToSingleError(Throwable ex, DataFetchingEnvironment env) { + + log.warn("exception is : {}", ex.getMessage()); + var message = ex.getMessage(); + var errorType = ErrorType.INTERNAL_ERROR; + + var startIndex = message.indexOf("{"); + var endIndex = message.indexOf("}") + 2; + if (startIndex != -1) { + var substring = message.substring(startIndex, endIndex); + + var json = new JSONObject(substring); + if (json.has(KEY_MESSAGE)) + message = json.getString(KEY_MESSAGE); + if (json.has(KEY_STATUS)) + errorType = ErrorType.valueOf(json.getString(KEY_STATUS)); + log.warn("GetMessage is : {}", message); + log.warn("GetCause is : {}", errorType); + } + log.warn(" ErrorType.valueOf(keyStatus) {}" + errorType); + if (ex instanceof Exception) { + return GraphqlErrorBuilder.newError() + .errorType(errorType) + .message(message) + .path(env.getExecutionStepInfo().getPath()) + .location(env.getField().getSourceLocation()) + .build(); + } + + return null; + + } +} diff --git a/dco-gateway/app/src/main/java/com/tsystems/dco/scenario/ScenarioClient.java b/dco-gateway/app/src/main/java/com/tsystems/dco/scenario/ScenarioClient.java new file mode 100644 index 0000000000000000000000000000000000000000..6b3fc02971a736400a026e34c499cf08e619b501 --- /dev/null +++ b/dco-gateway/app/src/main/java/com/tsystems/dco/scenario/ScenarioClient.java @@ -0,0 +1,44 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.scenario; + +import com.tsystems.dco.scenario.model.Scenario; +import com.tsystems.dco.scenario.model.ScenarioInput; +import com.tsystems.dco.scenario.model.ScenarioPage; +import org.springframework.web.multipart.MultipartFile; +import java.util.List; +import java.util.UUID; + +public interface ScenarioClient { + + Scenario createScenario(ScenarioInput scenarioInput, MultipartFile file); + + void deleteScenarioById(UUID id); + + Scenario updateScenario(UUID id, ScenarioInput scenarioInput, MultipartFile file); + + ScenarioPage scenarioReadByQuery(String query, String search, Integer page, Integer size, List<String> sort); + + ScenarioPage searchScenarioByPattern(String scenarioPattern, Integer page, Integer size); +} diff --git a/dco-gateway/app/src/main/java/com/tsystems/dco/scenario/ScenarioController.java b/dco-gateway/app/src/main/java/com/tsystems/dco/scenario/ScenarioController.java new file mode 100644 index 0000000000000000000000000000000000000000..52178fb645577aed3ec10672bf60d0fb2eca322f --- /dev/null +++ b/dco-gateway/app/src/main/java/com/tsystems/dco/scenario/ScenarioController.java @@ -0,0 +1,104 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.scenario; + +import com.tsystems.dco.scenario.model.ScenarioInput; +import com.tsystems.dco.scenario.model.ScenarioPage; +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.graphql.data.method.annotation.Argument; +import org.springframework.graphql.data.method.annotation.MutationMapping; +import org.springframework.graphql.data.method.annotation.QueryMapping; +import org.springframework.stereotype.Controller; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; +import java.util.UUID; + +@Controller +@RequiredArgsConstructor +public class ScenarioController { + + private static final Logger LOGGER = LoggerFactory.getLogger(ScenarioController.class); + + private final ScenarioClient scenarioClient; + + + /** + * @param file + * @param scenarioInput + * @return UUID + */ + @MutationMapping + public UUID createScenario(@Argument MultipartFile file, @Argument ScenarioInput scenarioInput) { + LOGGER.info("Scenario : {}", scenarioInput); + return scenarioClient.createScenario(scenarioInput, file).getId(); + } + + + /** + * @param query + * @param search + * @param page + * @param size + * @param sort + * @return ScenarioPage + */ + @QueryMapping + public ScenarioPage scenarioReadByQuery(@Argument String query, @Argument String search, @Argument Integer page, @Argument Integer size, @Argument List<String> sort) { + LOGGER.info("Scenario read "); + return scenarioClient.scenarioReadByQuery(query, search, page, size, sort); + } + + + /** + * @return String + */ + @MutationMapping + public String deleteScenarioById(@Argument UUID id) { + LOGGER.info("Scenario id : {}", id); + scenarioClient.deleteScenarioById(id); + return "Scenario deleted - " + id; + } + + /** + * @return UUID + */ + @MutationMapping + public UUID updateScenario(@Argument UUID id, @Argument MultipartFile file, @Argument ScenarioInput scenarioInput) { + LOGGER.info("Scenario : {}", scenarioInput); + return scenarioClient.updateScenario(id, scenarioInput, file).getId(); + } + + /** + * @return ScenarioPage + */ + @QueryMapping + public ScenarioPage searchScenarioByPattern(@Argument String scenarioPattern, @Argument Integer page, @Argument Integer size) { + LOGGER.info("Scenario search by pattern : {}", scenarioPattern); + return scenarioClient.searchScenarioByPattern(scenarioPattern, page, size); + } + +} diff --git a/dco-gateway/app/src/main/java/com/tsystems/dco/scenario/ScenarioRestClient.java b/dco-gateway/app/src/main/java/com/tsystems/dco/scenario/ScenarioRestClient.java new file mode 100644 index 0000000000000000000000000000000000000000..850e23e8eddf5b40369676527ca00b6e4a35bf59 --- /dev/null +++ b/dco-gateway/app/src/main/java/com/tsystems/dco/scenario/ScenarioRestClient.java @@ -0,0 +1,116 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.scenario; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.tsystems.dco.scenario.feign.ScenarioFeignClient; +import com.tsystems.dco.scenario.model.Scenario; +import com.tsystems.dco.scenario.model.ScenarioInput; +import com.tsystems.dco.scenario.model.ScenarioPage; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; +import java.util.List; +import java.util.UUID; + +@Component +@RequiredArgsConstructor +public class ScenarioRestClient implements ScenarioClient { + + private static final Logger LOGGER = LoggerFactory.getLogger(ScenarioRestClient.class); + + private final ScenarioFeignClient client; + + + /** + * @param scenarioInput + * @param file + * @return Scenario + */ + @SneakyThrows + @Override + public Scenario createScenario(ScenarioInput scenarioInput, MultipartFile file) { + var mapper = new ObjectMapper(); + String scenario = mapper.writeValueAsString(scenarioInput); + ResponseEntity<Scenario> scenarioResponseEntity = client.createScenario(scenario, file); + LOGGER.debug("Received response for createScenario : {}", scenarioResponseEntity.getStatusCode()); + return scenarioResponseEntity.getBody(); + } + + /** + * @param id + */ + @Override + public void deleteScenarioById(UUID id) { + client.deleteScenarioById(id); + } + + /** + * @param id + * @param scenarioInput + * @param file + * @return Scenario + */ + @SneakyThrows + @Override + public Scenario updateScenario(UUID id, ScenarioInput scenarioInput, MultipartFile file) { + var mapper = new ObjectMapper(); + String scenario = mapper.writeValueAsString(scenarioInput); + ResponseEntity<Scenario> scenarioResponseEntity = client.scenarioUpdateById(id, scenario, file); + LOGGER.debug("Received response for updateScenario : {}", scenarioResponseEntity.getStatusCode()); + return scenarioResponseEntity.getBody(); + } + + /** + * @param query + * @param search + * @param page + * @param size + * @param sort + * @return ScenarioPage + */ + @Override + public ScenarioPage scenarioReadByQuery(String query, String search, Integer page, Integer size, List<String> sort) { + ResponseEntity<ScenarioPage> scenarioPageResponseEntity = client.scenarioReadByQuery(query, search, page, size, sort); + LOGGER.debug("Received response for scenarioReadByQuery : {}", scenarioPageResponseEntity.getStatusCode()); + return scenarioPageResponseEntity.getBody(); + } + + /** + * @param scenarioPattern + * @param page + * @param size + * @return ScenarioPage + */ + @Override + public ScenarioPage searchScenarioByPattern(String scenarioPattern, Integer page, Integer size) { + ResponseEntity<ScenarioPage> scenarioPageResponseEntity = client.searchScenarioByPattern(scenarioPattern, page, size); + LOGGER.debug("Received response for searchScenarioByPattern : {}", scenarioPageResponseEntity.getStatusCode()); + return scenarioPageResponseEntity.getBody(); + } +} diff --git a/dco-gateway/app/src/main/java/com/tsystems/dco/scenario/SimulationClient.java b/dco-gateway/app/src/main/java/com/tsystems/dco/scenario/SimulationClient.java new file mode 100644 index 0000000000000000000000000000000000000000..9b22ea0bca89a0941c555bda90ac0705468fd056 --- /dev/null +++ b/dco-gateway/app/src/main/java/com/tsystems/dco/scenario/SimulationClient.java @@ -0,0 +1,35 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.scenario; + +import com.tsystems.dco.scenario.model.SimulationInput; +import com.tsystems.dco.scenario.model.SimulationPage; +import java.util.List; + +public interface SimulationClient { + + String launchSimulation(SimulationInput simulationInput); + + SimulationPage simulationReadByQuery(String query, String search, Integer page, Integer size, List<String> sort); +} diff --git a/dco-gateway/app/src/main/java/com/tsystems/dco/scenario/SimulationController.java b/dco-gateway/app/src/main/java/com/tsystems/dco/scenario/SimulationController.java new file mode 100644 index 0000000000000000000000000000000000000000..f9c20056c75abea80059b56f6fa9656597d585c6 --- /dev/null +++ b/dco-gateway/app/src/main/java/com/tsystems/dco/scenario/SimulationController.java @@ -0,0 +1,71 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.scenario; + +import com.tsystems.dco.scenario.model.SimulationInput; +import com.tsystems.dco.scenario.model.SimulationPage; +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.graphql.data.method.annotation.Argument; +import org.springframework.graphql.data.method.annotation.MutationMapping; +import org.springframework.graphql.data.method.annotation.QueryMapping; +import org.springframework.stereotype.Controller; + +import java.util.List; + +@Controller +@RequiredArgsConstructor +public class SimulationController { + + private static final Logger LOGGER = LoggerFactory.getLogger(SimulationController.class); + + private final SimulationClient simulationClient; + + + /** + * @param simulationInput + * @return String + */ + @MutationMapping + public String launchSimulation(@Argument SimulationInput simulationInput) { + LOGGER.info("Simulation Input : {}", simulationInput); + return simulationClient.launchSimulation(simulationInput); + } + + /** + * @param query + * @param search + * @param page + * @param size + * @param sort + * @return SimulationPage + */ + @QueryMapping + public SimulationPage simulationReadByQuery(@Argument String query, @Argument String search, @Argument Integer page, @Argument Integer size, @Argument List<String> sort) { + LOGGER.info("simulation read by query"); + return simulationClient.simulationReadByQuery(query, search, page, size, sort); + } + +} diff --git a/dco-gateway/app/src/main/java/com/tsystems/dco/scenario/SimulationRestClient.java b/dco-gateway/app/src/main/java/com/tsystems/dco/scenario/SimulationRestClient.java new file mode 100644 index 0000000000000000000000000000000000000000..8c556280d3e5a807a2bb8dc54abc39f6a94d3da7 --- /dev/null +++ b/dco-gateway/app/src/main/java/com/tsystems/dco/scenario/SimulationRestClient.java @@ -0,0 +1,65 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.scenario; + +import com.tsystems.dco.scenario.feign.ScenarioFeignClient; +import com.tsystems.dco.scenario.model.SimulationInput; +import com.tsystems.dco.scenario.model.SimulationPage; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +@RequiredArgsConstructor +public class SimulationRestClient implements SimulationClient { + + private final ScenarioFeignClient client; + + + /** + * @param simulationInput + * @return String + */ + @Override + public String launchSimulation(SimulationInput simulationInput) { + ResponseEntity<String> responseEntity = client.launchSimulation(simulationInput); + return responseEntity.getBody(); + } + + /** + * @param query + * @param search + * @param page + * @param size + * @param sort + * @return SimulationPage + */ + @Override + public SimulationPage simulationReadByQuery(String query, String search, Integer page, Integer size, List<String> sort) { + ResponseEntity<SimulationPage> simulationPageResponseEntity = client.simulationReadByQuery(query, search, page, size, sort); + return simulationPageResponseEntity.getBody(); + } +} diff --git a/dco-gateway/app/src/main/java/com/tsystems/dco/scenario/feign/FeignClientConfiguration.java b/dco-gateway/app/src/main/java/com/tsystems/dco/scenario/feign/FeignClientConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..1b4068a782a1d1ff60a58fbb06a8ef9ef398530b --- /dev/null +++ b/dco-gateway/app/src/main/java/com/tsystems/dco/scenario/feign/FeignClientConfiguration.java @@ -0,0 +1,44 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + + +package com.tsystems.dco.scenario.feign; + +import feign.auth.BasicAuthRequestInterceptor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class FeignClientConfiguration { + + @Value("${app.username}") + private String username; + @Value("${app.password}") + private String password; + + @Bean + public BasicAuthRequestInterceptor basicAuthRequestInterceptor() { + return new BasicAuthRequestInterceptor(username, password); + } +} diff --git a/dco-gateway/app/src/main/java/com/tsystems/dco/scenario/feign/ScenarioFeignClient.java b/dco-gateway/app/src/main/java/com/tsystems/dco/scenario/feign/ScenarioFeignClient.java new file mode 100644 index 0000000000000000000000000000000000000000..5e47140422c4ae99a30d22aca40fc198a0d633dd --- /dev/null +++ b/dco-gateway/app/src/main/java/com/tsystems/dco/scenario/feign/ScenarioFeignClient.java @@ -0,0 +1,77 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.scenario.feign; + +import com.tsystems.dco.scenario.model.Scenario; +import com.tsystems.dco.scenario.model.ScenarioPage; +import com.tsystems.dco.scenario.model.SimulationInput; +import com.tsystems.dco.scenario.model.SimulationPage; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import java.util.List; +import java.util.UUID; + +@FeignClient( + name = "scenario", + url = "${app.scenario.rest.url}", + configuration = FeignClientConfiguration.class +) +public interface ScenarioFeignClient { + + @PostMapping(value = "/api/scenario", produces = {"application/json"}, consumes = {"multipart/form-data"}) + ResponseEntity<Scenario> createScenario(@RequestPart(value = "scenario") String scenario, + @RequestPart(value = "file") MultipartFile file); + + @DeleteMapping(value = "/api/scenario") + ResponseEntity<Void> deleteScenarioById(@RequestParam(value = "id") UUID id); + + @GetMapping(value = "/api/scenario", produces = {"application/json"}) + ResponseEntity<ScenarioPage> scenarioReadByQuery(@RequestParam(value = "query") String query, + @RequestParam(value = "search") String search, + @RequestParam(value = "page") Integer page, + @RequestParam(value = "size") Integer size, + @RequestParam(value = "sort") List<String> sort); + + @PutMapping(value = "/api/scenario", produces = {"application/json"}, consumes = {"multipart/form-data"}) + ResponseEntity<Scenario> scenarioUpdateById(@RequestParam(value = "id") UUID id, + @RequestPart(value = "scenario") String scenario, + @RequestPart(value = "file") MultipartFile file); + + @GetMapping(value = "/api/scenario/search", produces = {"application/json"}) + ResponseEntity<ScenarioPage> searchScenarioByPattern(@RequestParam(value = "scenarioPattern") String scenarioPattern, + @RequestParam(value = "page") Integer page, + @RequestParam(value = "size") Integer size); + + @PostMapping(value = "/api/simulation", produces = {"application/json"}, consumes = {"application/json"}) + ResponseEntity<String> launchSimulation(@RequestBody SimulationInput simulationInput); + + @GetMapping(value = "/api/simulation", produces = {"application/json"}) + ResponseEntity<SimulationPage> simulationReadByQuery(@RequestParam(value = "query") String query, + @RequestParam(value = "search") String search, + @RequestParam(value = "page") Integer page, + @RequestParam(value = "size") Integer size, + @RequestParam(value = "sort") List<String> sort); +} diff --git a/dco-gateway/app/src/main/java/com/tsystems/dco/track/TrackClient.java b/dco-gateway/app/src/main/java/com/tsystems/dco/track/TrackClient.java new file mode 100644 index 0000000000000000000000000000000000000000..d945d402066425c8b77c13fc19ba204e12ba044b --- /dev/null +++ b/dco-gateway/app/src/main/java/com/tsystems/dco/track/TrackClient.java @@ -0,0 +1,47 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.track; + +import com.tsystems.dco.track.model.*; +import java.util.List; +import java.util.UUID; + +public interface TrackClient { + + Track createTrack(TrackInput trackInput); + + String deleteTrackById(UUID id); + + Track findTrackById(UUID id); + + TrackPage searchTrackByPattern(String trackPattern, Integer page, Integer size); + + TrackPage trackReadByQuery(String query, String search, Integer page, Integer size, List<String> sort); + + VehiclePage vehicleReadByQuery(String query, String search, Integer page, Integer size, List<String> sort); + + List<String> getHardwareModule(); + + VehicleResponse getVehicleByVin(String vin); +} diff --git a/dco-gateway/app/src/main/java/com/tsystems/dco/track/TrackController.java b/dco-gateway/app/src/main/java/com/tsystems/dco/track/TrackController.java new file mode 100644 index 0000000000000000000000000000000000000000..1179f95cd368e37b747cb4fb30f30646f008a713 --- /dev/null +++ b/dco-gateway/app/src/main/java/com/tsystems/dco/track/TrackController.java @@ -0,0 +1,138 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.track; + +import com.tsystems.dco.track.model.*; +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.graphql.data.method.annotation.Argument; +import org.springframework.graphql.data.method.annotation.MutationMapping; +import org.springframework.graphql.data.method.annotation.QueryMapping; +import org.springframework.stereotype.Controller; + +import java.util.List; +import java.util.UUID; + +@Controller +@RequiredArgsConstructor +public class TrackController { + + private static final Logger LOGGER = LoggerFactory.getLogger(TrackController.class); + + private final TrackClient trackClient; + + /** + * @param trackInput + * @return Track + */ + @MutationMapping + public Track createTrack(@Argument TrackInput trackInput) { + LOGGER.info("TrackInput : {}", trackInput); + return trackClient.createTrack(trackInput); + } + + /** + * @param query + * @param search + * @param page + * @param size + * @param sort + * @return TrackPage + */ + @QueryMapping + public TrackPage trackReadByQuery(@Argument String query, @Argument String search, @Argument Integer page, @Argument Integer size, @Argument List<String> sort) { + LOGGER.info("Track read by query"); + return trackClient.trackReadByQuery(query, search, page, size, sort); + } + + + /** + * @param id + * @return String + */ + @MutationMapping + public String deleteTrackById(@Argument UUID id) { + LOGGER.info("Deleting Track by id : {}", id); + trackClient.deleteTrackById(id); + return "Track deleted - " + id; + } + + /** + * @param id + * @return Track + */ + @QueryMapping + public Track findTrackById(@Argument UUID id) { + LOGGER.info("Track by id : {}", id); + return trackClient.findTrackById(id); + } + + + /** + * @param trackPattern + * @param page + * @param size + * @return TrackPage + */ + @QueryMapping + public TrackPage searchTrackByPattern(@Argument String trackPattern, @Argument Integer page, @Argument Integer size) { + LOGGER.info("Track search by Pattern : {}", trackPattern); + return trackClient.searchTrackByPattern(trackPattern, page, size); + } + + + /** + * @param query + * @param search + * @param page + * @param size + * @param sort + * @return VehiclePage + */ + @QueryMapping + public VehiclePage vehicleReadByQuery(@Argument String query, @Argument String search, @Argument Integer page, @Argument Integer size, @Argument List<String> sort) { + LOGGER.info("Track read by query"); + return trackClient.vehicleReadByQuery(query, search, page, size, sort); + } + + /** + * @return List + */ + @QueryMapping + public List<String> getHardwareModule() { + LOGGER.info("read hardware module"); + return trackClient.getHardwareModule(); + } + + /** + * @param vin + * @return VehicleResponse + */ + @QueryMapping + public VehicleResponse getVehicleByVin(@Argument String vin) { + return trackClient.getVehicleByVin(vin); + } + +} diff --git a/dco-gateway/app/src/main/java/com/tsystems/dco/track/TrackRestClient.java b/dco-gateway/app/src/main/java/com/tsystems/dco/track/TrackRestClient.java new file mode 100644 index 0000000000000000000000000000000000000000..3cba76f16e1f7982e13cbb44a18e645e6396cd5f --- /dev/null +++ b/dco-gateway/app/src/main/java/com/tsystems/dco/track/TrackRestClient.java @@ -0,0 +1,126 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.track; + +import com.tsystems.dco.track.feign.TrackFeignClient; +import com.tsystems.dco.track.model.*; +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.UUID; + +@Component +@RequiredArgsConstructor +public class TrackRestClient implements TrackClient { + + private static final Logger LOGGER = LoggerFactory.getLogger(TrackRestClient.class); + + private final TrackFeignClient client; + + /** + * @param trackInput + * @return Track + */ + @Override + public Track createTrack(TrackInput trackInput) { + return client.createTrack(trackInput).getBody(); + } + + /** + * @param id + * @return String + */ + @Override + public String deleteTrackById(UUID id) { + return client.deleteTrackById(id).getBody(); + } + + + /** + * @param id + * @return Track + */ + @Override + public Track findTrackById(UUID id) { + return client.findTrackById(id).getBody(); + } + + /** + * @param trackPattern + * @param page + * @param size + * @return TrackPage + */ + @Override + public TrackPage searchTrackByPattern(String trackPattern, Integer page, Integer size) { + return client.searchTrackByPattern(trackPattern, page, size).getBody(); + } + + /** + * @param query + * @param search + * @param page + * @param size + * @param sort + * @return TrackPage + */ + @Override + public TrackPage trackReadByQuery(String query, String search, Integer page, Integer size, List<String> sort) { + return client.trackReadByQuery(query, search, page, size, sort).getBody(); + } + + /** + * @param query + * @param search + * @param page + * @param size + * @param sort + * @return VehiclePage + */ + @Override + public VehiclePage vehicleReadByQuery(String query, String search, Integer page, Integer size, List<String> sort) { + return client.vehicleReadByQuery(query, search, page, size, sort).getBody(); + } + + /** + * @return List + */ + @Override + public List<String> getHardwareModule() { + return client.getHardwareModule().getBody(); + } + + /** + * @param vin + * @return VehicleResponse + */ + @Override + public VehicleResponse getVehicleByVin(String vin) { + return client.getVehicleByVin(vin).getBody(); + } + +} diff --git a/dco-gateway/app/src/main/java/com/tsystems/dco/track/feign/TrackFeignClient.java b/dco-gateway/app/src/main/java/com/tsystems/dco/track/feign/TrackFeignClient.java new file mode 100644 index 0000000000000000000000000000000000000000..1289140c2de96c6e0f3e95e579c5cdab5a9265f7 --- /dev/null +++ b/dco-gateway/app/src/main/java/com/tsystems/dco/track/feign/TrackFeignClient.java @@ -0,0 +1,75 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.track.feign; + +import com.tsystems.dco.scenario.feign.FeignClientConfiguration; +import com.tsystems.dco.track.model.*; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import java.util.List; +import java.util.UUID; + +@FeignClient( + name = "track", + url = "${app.track.rest.url}", + configuration = FeignClientConfiguration.class +) +public interface TrackFeignClient { + + @PostMapping(value = "/api/track", produces = {"application/json"}, consumes = {"application/json"}) + ResponseEntity<Track> createTrack(@RequestBody TrackInput trackInput); + + @DeleteMapping(value = "/api/track", produces = {"application/json"}) + ResponseEntity<String> deleteTrackById(@RequestParam(value = "id") UUID id); + + @GetMapping(value = "/api/track/{id}", produces = {"application/json"}) + ResponseEntity<Track> findTrackById(@PathVariable("id") UUID id); + + @GetMapping(value = "/api/track/search", produces = {"application/json"}) + ResponseEntity<TrackPage> searchTrackByPattern(@RequestParam(value = "trackPattern") String trackPattern, + @RequestParam(value = "page") Integer page, + @RequestParam(value = "size") Integer size); + + @GetMapping(value = "/api/track", produces = {"application/json"}) + ResponseEntity<TrackPage> trackReadByQuery(@RequestParam(value = "query") String query, + @RequestParam(value = "search") String search, + @RequestParam(value = "page") Integer page, + @RequestParam(value = "size") Integer size, + @RequestParam(value = "sort") List<String> sort); + + @GetMapping(value = "/api/vehicle", produces = {"application/json"}) + ResponseEntity<VehiclePage> vehicleReadByQuery(@RequestParam(value = "query") String query, + @RequestParam(value = "search") String search, + @RequestParam(value = "page") Integer page, + @RequestParam(value = "size") Integer size, + @RequestParam(value = "sort") List<String> sort); + + @GetMapping(value = "/api/track/hardware", produces = {"application/json"}) + ResponseEntity<List<String>> getHardwareModule(); + + @GetMapping(value = "/api/vehicle/{vin}", produces = { "application/json" }) + ResponseEntity<VehicleResponse> getVehicleByVin(@PathVariable("vin") String vin); + +} diff --git a/dco-gateway/app/src/main/resources/application.yml b/dco-gateway/app/src/main/resources/application.yml new file mode 100644 index 0000000000000000000000000000000000000000..1bb5df24fe3acf57e0db10bb7d3e04e75b169227 --- /dev/null +++ b/dco-gateway/app/src/main/resources/application.yml @@ -0,0 +1,108 @@ +app: + rest: + port: 8080 + probes: + port: 8080 + cors: + origins: "*" + headers: "*" + track: + rest: + url: ${TRACK_MANAGEMENT_URL:http://localhost:8081} + scenario: + rest: + url: ${SCENARIO_LIBRARY_URL:http://localhost:8082} + username: developer + password: password +server: + port: ${app.rest.port} + forward-headers-strategy: FRAMEWORK +spring: + servlet: + multipart: + max-request-size: -1 + enabled: true + graphql: + path: /graphql + graphiql: + enabled: true + schema: + locations: classpath:graphql/**/ + printer: + enabled: true + cors: + allowed-origins: ${app.cors.origins} + allowed-headers: ${app.cors.headers} +management: + server: + port: ${app.probes.port} + info: + env: + enabled: true + build: + enabled: false + endpoint: + info: + enabled: true + health: + enabled: true + metrics: + enabled: true + prometheus: + enabled: true + endpoints: + enabled-by-default: false + web: + base-path: /management + exposure: + include: info,health,metrics,prometheus + metrics: + export: + prometheus: + descriptions: false + step: 30s +info: + app: + name: ${app.name} + version: ${app.version} + profiles: ${spring.profiles.active} +graphql: + servlet: + corsEnabled: true + playground: + mapping: /playground + endpoint: /graphql + enabled: true + headers: + Authorization: "Basic ZGV2ZWxvcGVyOnBhc3N3b3Jk" #Base64 encoded username and password(developer:password) + cdn: + enabled: true + version: 1.7.28 + tabs: + - name: Get Scenarios + query: classpath:graphql-test/scenario_list.graphql + - name: Delete Scenario + query: classpath:graphql-test/scenario_delete.graphql + - name: Search Scenario + query: classpath:graphql-test/scenario_search.graphql + - name: Get Vehicles + query: classpath:graphql-test/vehicle_list.graphql + - name: Create Track + query: classpath:graphql-test/track_create.graphql + - name: Get Tracks + query: classpath:graphql-test/track_list.graphql + - name: Track By Id + query: classpath:graphql-test/track_id.graphql + - name: Delete Track + query: classpath:graphql-test/track_delete.graphql + - name: Search Track + query: classpath:graphql-test/track_search.graphql + - name: Launch Simulation + query: classpath:graphql-test/simulation_launch.graphql + - name: Get Simulations + query: classpath:graphql-test/simulation_list.graphql + - name: Get Hardware Module + query: classpath:graphql-test/track_hardware.graphql + - name: Vehicle by VIN + query: classpath:graphql-test/vehicle_vin.graphql + diff --git a/dco-gateway/app/src/main/resources/graphql-test/scenario_delete.graphql b/dco-gateway/app/src/main/resources/graphql-test/scenario_delete.graphql new file mode 100644 index 0000000000000000000000000000000000000000..1fadc1daa786f9c7416186c9c94bf919081afe4d --- /dev/null +++ b/dco-gateway/app/src/main/resources/graphql-test/scenario_delete.graphql @@ -0,0 +1,3 @@ +mutation DELETE_SCENARIO { + deleteScenarioById(id: "9711f69b-8210-42d0-a6e6-5397d4bd5d99") +} diff --git a/dco-gateway/app/src/main/resources/graphql-test/scenario_list.graphql b/dco-gateway/app/src/main/resources/graphql-test/scenario_list.graphql new file mode 100644 index 0000000000000000000000000000000000000000..f7fb289e0a48b498549dfc2e58ab61bae61616e2 --- /dev/null +++ b/dco-gateway/app/src/main/resources/graphql-test/scenario_list.graphql @@ -0,0 +1,31 @@ +query GET_SCENARIO { + scenarioReadByQuery(search: null, query: null, page: 0, size: 2, sort: null){ + content { + id + name + description + type + status + createdBy + createdAt + lastModifiedBy + lastModifiedAt + file { + id + path + size + checksum + updatedBy + updatedOn + } + } + empty + first + last + page + size + pages + elements + total + } +} diff --git a/dco-gateway/app/src/main/resources/graphql-test/scenario_search.graphql b/dco-gateway/app/src/main/resources/graphql-test/scenario_search.graphql new file mode 100644 index 0000000000000000000000000000000000000000..3306a2ceb43883c113c9480e2df9f37f52608180 --- /dev/null +++ b/dco-gateway/app/src/main/resources/graphql-test/scenario_search.graphql @@ -0,0 +1,31 @@ +query SEARCH_SCENARIO { + searchScenarioByPattern(scenarioPattern: "Scenario", page:0, size:11){ + content{ + id + name + description + type + status + createdBy + createdAt + lastModifiedBy + lastModifiedAt + file { + id + path + size + checksum + updatedBy + updatedOn + } + } + empty + first + last + page + size + pages + elements + total + } +} diff --git a/dco-gateway/app/src/main/resources/graphql-test/simulation_launch.graphql b/dco-gateway/app/src/main/resources/graphql-test/simulation_launch.graphql new file mode 100644 index 0000000000000000000000000000000000000000..e3d0b3b958d7c30fc66fcb0afa721f2e0bbd6253 --- /dev/null +++ b/dco-gateway/app/src/main/resources/graphql-test/simulation_launch.graphql @@ -0,0 +1,15 @@ +mutation LAUNCH_SIMULATION { + launchSimulation( + simulationInput: { + name: "Test Simulation" + environment: "Development Demo" + platform: "Task Management" + scenarioType: "Over-The-Air Service" + hardware: "TEST_OTA" + description: "Test Simulation Data" + tracks: ["bd6ed64b-0547-42b4-9769-d0a0ea095070", "5a955942-6169-4b0f-a26b-b4585d19ed55"] + scenarios: ["74fef2c0-4efc-42bb-b064-da7371421c8b"] + createdBy: "Kashif.Kamal" + } + ) +} diff --git a/dco-gateway/app/src/main/resources/graphql-test/simulation_list.graphql b/dco-gateway/app/src/main/resources/graphql-test/simulation_list.graphql new file mode 100644 index 0000000000000000000000000000000000000000..93dab96a79772b09550b1fe3be9d16b01f223036 --- /dev/null +++ b/dco-gateway/app/src/main/resources/graphql-test/simulation_list.graphql @@ -0,0 +1,27 @@ +query LIST_SIMULATION { + simulationReadByQuery(search: null, query: null, page: 0, size: 2, sort: null){ + content{ + id + name + status + environment + platform + scenarioType + noOfVehicle + noOfScenarios + brands + hardware + description + createdBy + startDate + } + empty + first + last + page + size + pages + elements + total + } +} diff --git a/dco-gateway/app/src/main/resources/graphql-test/track_create.graphql b/dco-gateway/app/src/main/resources/graphql-test/track_create.graphql new file mode 100644 index 0000000000000000000000000000000000000000..9029fbbdf83ea484fa4e89131a75cb31fc1cf5ac --- /dev/null +++ b/dco-gateway/app/src/main/resources/graphql-test/track_create.graphql @@ -0,0 +1,24 @@ +mutation CREATE_TRACK { + createTrack ( + trackInput: { + name: "Test Track" + trackType: "Test" + duration: "7" + state: "CREATED" + description: "Test Desc" + vehicles: [ + { + vin: "VINTEST1HQMIOUT08" + country: "DE" + } + ] + } + ){ + id + name + trackType + state + description + duration + } +} diff --git a/dco-gateway/app/src/main/resources/graphql-test/track_delete.graphql b/dco-gateway/app/src/main/resources/graphql-test/track_delete.graphql new file mode 100644 index 0000000000000000000000000000000000000000..d403ada13ea13a9841467c73a0022241dd48e12b --- /dev/null +++ b/dco-gateway/app/src/main/resources/graphql-test/track_delete.graphql @@ -0,0 +1,3 @@ +mutation DELETE_TRACK { + deleteTrackById(id : "e8e996c5-8081-43c9-9d97-cb170eb0eee5") +} \ No newline at end of file diff --git a/dco-gateway/app/src/main/resources/graphql-test/track_hardware.graphql b/dco-gateway/app/src/main/resources/graphql-test/track_hardware.graphql new file mode 100644 index 0000000000000000000000000000000000000000..db38616dc92cbe6d72e13bc32a27e02812121980 --- /dev/null +++ b/dco-gateway/app/src/main/resources/graphql-test/track_hardware.graphql @@ -0,0 +1,3 @@ +query GET_HARDWARE_MODULE { + getHardwareModule +} diff --git a/dco-gateway/app/src/main/resources/graphql-test/track_id.graphql b/dco-gateway/app/src/main/resources/graphql-test/track_id.graphql new file mode 100644 index 0000000000000000000000000000000000000000..a3fe1c6fb947d22b7ea30863847e99da9f4f8076 --- /dev/null +++ b/dco-gateway/app/src/main/resources/graphql-test/track_id.graphql @@ -0,0 +1,36 @@ +query TRACK_BY_ID { + findTrackById(id : "818094c5-0be4-4a0d-bf9f-0c70919d05ee") { + id + name + state + trackType + duration + description + vehicles{ + vin + country + brand + status + updatedAt + devices { + id + type + status + createdAt + gatewayId + modelType + dmProtocol + modifiedAt + dmProtocolVersion + serialNumber + components{ + id + name + status + version + environmentType + } + } + } + } +} \ No newline at end of file diff --git a/dco-gateway/app/src/main/resources/graphql-test/track_list.graphql b/dco-gateway/app/src/main/resources/graphql-test/track_list.graphql new file mode 100644 index 0000000000000000000000000000000000000000..57de86074ca52b46bfb62afb510736750029c076 --- /dev/null +++ b/dco-gateway/app/src/main/resources/graphql-test/track_list.graphql @@ -0,0 +1,22 @@ +query LIST_TRACK { + trackReadByQuery(search: null, query: null, page: 0, size: 2, sort: null){ + content{ + id + name + state + trackType + vehicles { + vin + country + } + } + empty + first + last + page + size + pages + elements + total + } +} diff --git a/dco-gateway/app/src/main/resources/graphql-test/track_search.graphql b/dco-gateway/app/src/main/resources/graphql-test/track_search.graphql new file mode 100644 index 0000000000000000000000000000000000000000..8ed3c16c9218a864f1217d3ec36c862922e4e42d --- /dev/null +++ b/dco-gateway/app/src/main/resources/graphql-test/track_search.graphql @@ -0,0 +1,20 @@ +query SEARCH_TRACK { + searchTrackByPattern(trackPattern: "eu", page:0, size:11){ + content{ + id + name + trackType + state + description + duration + } + empty + first + last + page + size + pages + elements + total + } +} diff --git a/dco-gateway/app/src/main/resources/graphql-test/vehicle_list.graphql b/dco-gateway/app/src/main/resources/graphql-test/vehicle_list.graphql new file mode 100644 index 0000000000000000000000000000000000000000..b6983790efbc84103ac4d118277dd08003b6fcc8 --- /dev/null +++ b/dco-gateway/app/src/main/resources/graphql-test/vehicle_list.graphql @@ -0,0 +1,46 @@ +query LIST_VEHICLE { + vehicleReadByQuery(search: null, query: null, page: 0, size: 2, sort: null){ + content{ + vin + owner + ecomDate + country + model + brand + region + instantiatedAt + createdAt + updatedAt + status + type + fleets { + id + name + type + } + services { + serviceId + operation + updatedAt + } + devices { + id + type + status + createdAt + gatewayId + dmProtocol + modifiedAt + dmProtocolVersion + } + } + empty + first + last + page + size + pages + elements + total + } +} diff --git a/dco-gateway/app/src/main/resources/graphql-test/vehicle_vin.graphql b/dco-gateway/app/src/main/resources/graphql-test/vehicle_vin.graphql new file mode 100644 index 0000000000000000000000000000000000000000..2b5b0736380486b252c54df25cdede4229695ff9 --- /dev/null +++ b/dco-gateway/app/src/main/resources/graphql-test/vehicle_vin.graphql @@ -0,0 +1,7 @@ +query VEHICLE_BY_VIN{ + getVehicleByVin(vin: "BBTEST00000000340"){ + vin + model + brand + } +} diff --git a/dco-gateway/app/src/main/resources/graphql/mutation.graphqls b/dco-gateway/app/src/main/resources/graphql/mutation.graphqls new file mode 100644 index 0000000000000000000000000000000000000000..035749c061c878fa9fb0921a961e93f67d890331 --- /dev/null +++ b/dco-gateway/app/src/main/resources/graphql/mutation.graphqls @@ -0,0 +1,56 @@ +scalar Upload + +type Mutation{ + createScenario(file: Upload!, scenarioInput: ScenarioInput): ID + updateScenario(id: ID!, file: Upload, scenarioInput: ScenarioInput): ID + deleteScenarioById(id: ID!): String + createTrack(trackInput: TrackInput): Track + deleteTrackById(id: ID!): String + launchSimulation(simulationInput: SimulationInput): String +} + +input ScenarioInput { + name: String + status: StatusEnum + type: TypeEnum + description: String + createdBy: String + lastModifiedBy: String +} + +input TrackInput { + name: String! + trackType: String + state: String + duration: String! + description: String + componentId: ID + vehicles: [Vehicle] +} + +input Vehicle { + vin: String + country: String +} + +enum StatusEnum { + CREATED, + ARCHIVED +} + +enum TypeEnum { + MQTT, + CAN +} + +input SimulationInput { + name: String! + environment: String + platform: String + scenarioType: String! + hardware: String + description: String + tracks: [ID!]! + scenarios: [ID!]! + createdBy: String +} diff --git a/dco-gateway/app/src/main/resources/graphql/query.graphqls b/dco-gateway/app/src/main/resources/graphql/query.graphqls new file mode 100644 index 0000000000000000000000000000000000000000..62ec5e68afc4e82391198493845cb7d6819f95c2 --- /dev/null +++ b/dco-gateway/app/src/main/resources/graphql/query.graphqls @@ -0,0 +1,138 @@ +type Query { + scenarioReadByQuery(query: String, search: String, page: Int, size: Int, sort: [String]): ScenarioPage + searchScenarioByPattern(scenarioPattern: String, page: Int, size: Int): ScenarioPage + trackReadByQuery(query: String, search: String, page: Int, size: Int, sort: [String]): TrackPage + searchTrackByPattern(trackPattern: String, page: Int, size: Int): TrackPage + vehicleReadByQuery(query: String, search: String, page: Int, size: Int, sort: [String]): VehiclePage + findTrackById(id: ID): Track + simulationReadByQuery(query: String, search: String, page: Int, size: Int, sort: [String]): SimulationPage + getHardwareModule: [String] + getVehicleByVin(vin: String): VehicleResponse +} + +type ScenarioPage { + content: [Scenario] + empty: Boolean + first: Boolean + last: Boolean + page: Int + size:Int + pages: Int + elements: Int + total: Int +} + +type TrackPage { + content: [Track] + empty: Boolean + first: Boolean + last: Boolean + page: Int + size:Int + pages: Int + elements: Int + total: Int +} + +type Track { + id: ID + name: String + trackType: String + state: String + duration: String + description: String + vehicles: [VehicleResponse] +} + +type VehicleResponse { + vin: String + owner:String + ecomDate: String + country : String + model: String + brand: String + region: String + instantiatedAt: String + createdAt: String + updatedAt: String + status: String + type: String + fleets: [FleetResponse] + services: [ServiceResponse] + devices: [DeviceResponse] + tags: [String] +} + +type FleetResponse { + id: String + name: String + type: String +} + +type ServiceResponse { + serviceId: String + operation: String + updatedAt: String +} + +type DeviceResponse { + id: String + type: String + status: String + createdAt: String + gatewayId: String + modelType: String + dmProtocol: String + modifiedAt: String + dmProtocolVersion: String + serialNumber: String + components: [DeviceComponentResponse] +} + +type DeviceComponentResponse { + id: String + name: String + status: String + version: String + environmentType: String +} + +type VehiclePage { + content: [VehicleResponse] + empty: Boolean + first: Boolean + last: Boolean + page: Int + size:Int + pages: Int + elements: Int + total: Int +} + +type SimulationPage { + content: [Simulation] + empty: Boolean + first: Boolean + last: Boolean + page: Int + size:Int + pages: Int + elements: Int + total: Int +} + +type Simulation { + id: ID + name: String + status: String + environment: String + platform: String + scenarioType: String + hardware: String + noOfVehicle: Int + noOfScenarios: Int + brands: [String] + description: String + createdBy: String + startDate: String +} diff --git a/dco-gateway/app/src/main/resources/graphql/schema.graphqls b/dco-gateway/app/src/main/resources/graphql/schema.graphqls new file mode 100644 index 0000000000000000000000000000000000000000..91b6205d8e010958d80c1761e41871e6307c6560 --- /dev/null +++ b/dco-gateway/app/src/main/resources/graphql/schema.graphqls @@ -0,0 +1,30 @@ +scalar ID + +schema { + query: Query + mutation: Mutation +} + +type Scenario { + id: ID + name: String + status: StatusEnum + type: TypeEnum + description: String + url: String + createdBy: String + createdAt: String + lastModifiedBy: String + lastModifiedAt: String + file: FileData +} + +type FileData { + id: ID + path: String + size: String + checksum: String + updatedBy: String + updatedOn: String + releaseNotes: String +} diff --git a/dco-gateway/app/src/test/java/com/tsystems/dco/scenario/ScenarioControllerTest.java b/dco-gateway/app/src/test/java/com/tsystems/dco/scenario/ScenarioControllerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..53cb91a26f87028d020d1616c6a8d60a5853c542 --- /dev/null +++ b/dco-gateway/app/src/test/java/com/tsystems/dco/scenario/ScenarioControllerTest.java @@ -0,0 +1,96 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.scenario; + +import com.tsystems.dco.scenario.model.ScenarioPage; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.graphql.tester.AutoConfigureGraphQlTester; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.graphql.test.tester.GraphQlTester; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) +@AutoConfigureGraphQlTester +class ScenarioControllerTest { + + @Autowired + private GraphQlTester tester; + + @MockBean + private ScenarioRestClient client; + + @Test + void scenarioReadByQuery() { + var tracks = """ + query GET_SCENARIO { + scenarioReadByQuery(search: null, query: null, page: 0, size: 2, sort: null){ + content { + id + name + file { + id + } + } + total + } + } + """; + when(client.scenarioReadByQuery(any(), any(), anyInt(), anyInt(), any())).thenReturn(new ScenarioPage()); + tester.document(tracks).execute().errors().satisfy(error -> assertThat(error.size() == 0)); + } + + @Test + void deleteScenarioById() { + var tracks = """ + mutation DELETE_SCENARIO { + deleteScenarioById(id: "9711f69b-8210-42d0-a6e6-5397d4bd5d99") + } + """; + tester.document(tracks).execute().errors().satisfy(error -> assertThat(error.size() == 0)); + } + + @Test + void searchScenarioByPattern() { + var tracks = """ + query SEARCH_SCENARIO { + searchScenarioByPattern(scenarioPattern: "Scenario", page:0, size:11){ + content{ + id + name + file { + id + } + } + total + } + } + """; + when(client.searchScenarioByPattern(any(), anyInt(), anyInt())).thenReturn(new ScenarioPage()); + tester.document(tracks).execute().errors().satisfy(error -> assertThat(error.size() == 0)); + } +} diff --git a/dco-gateway/app/src/test/java/com/tsystems/dco/scenario/ScenarioRestClientTest.java b/dco-gateway/app/src/test/java/com/tsystems/dco/scenario/ScenarioRestClientTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5371b9b3d7d6c166931b2542b45b78c5b999daaf --- /dev/null +++ b/dco-gateway/app/src/test/java/com/tsystems/dco/scenario/ScenarioRestClientTest.java @@ -0,0 +1,146 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.scenario; + +import com.tsystems.dco.scenario.feign.ScenarioFeignClient; +import com.tsystems.dco.scenario.model.Scenario; +import com.tsystems.dco.scenario.model.ScenarioInput; +import com.tsystems.dco.scenario.model.ScenarioPage; +import feign.FeignException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.web.multipart.MultipartFile; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.BDDMockito.*; +import static org.mockito.Mockito.verify; + +@ContextConfiguration(classes = {ScenarioRestClient.class}) +@ExtendWith(SpringExtension.class) +class ScenarioRestClientTest { + + @Autowired + private ScenarioRestClient scenarioRestClient; + @MockBean + private ScenarioFeignClient client; + @Mock + private ResponseEntity<ScenarioPage> scenarioPageResponseEntity; + @Mock + private ResponseEntity<Scenario> scenarioResponseEntity; + private final static String TEST = "Test"; + + @Test + void createScenario() throws IOException { + InputStream is = new ByteArrayInputStream(TEST.getBytes()); + MultipartFile file = new MockMultipartFile(TEST, is); + ScenarioInput scenarioInput = new ScenarioInput(); + Scenario scenario = new Scenario(); + given(scenarioResponseEntity.getBody()).willReturn(scenario); + given(client.createScenario(any(), any())).willReturn(scenarioResponseEntity); + assertSame(scenario, scenarioRestClient.createScenario(scenarioInput, file)); + verify(client).createScenario(any(), any()); + verify(scenarioResponseEntity).getBody(); + } + + @Test + void createScenarioWithError() throws IOException { + InputStream is = new ByteArrayInputStream(TEST.getBytes()); + MultipartFile file = new MockMultipartFile(TEST, is); + ScenarioInput scenarioInput = new ScenarioInput(); + Scenario scenario = new Scenario(); + given(scenarioResponseEntity.getBody()).willThrow(FeignException.class); + given(client.createScenario(any(), any())).willReturn(scenarioResponseEntity); + assertThrows(FeignException.class, () -> scenarioRestClient.createScenario(scenarioInput, file)); + } + @Test + void deleteScenarioById() { + scenarioRestClient.deleteScenarioById(UUID.randomUUID()); + verify(client).deleteScenarioById(any()); + } + + @Test + void updateScenario() throws IOException { + InputStream is = new ByteArrayInputStream(TEST.getBytes()); + MultipartFile file = new MockMultipartFile(TEST, is); + ScenarioInput scenarioInput = new ScenarioInput(); + Scenario scenario = new Scenario(); + given(scenarioResponseEntity.getBody()).willReturn(scenario); + given(client.scenarioUpdateById(any(), any(), any())).willReturn(scenarioResponseEntity); + assertSame(scenario, scenarioRestClient.updateScenario(UUID.randomUUID(), scenarioInput, file)); + verify(client).scenarioUpdateById(any(), any(), any()); + verify(scenarioResponseEntity).getBody(); + } + + @Test + void updateScenarioWithError() throws IOException { + InputStream is = new ByteArrayInputStream(TEST.getBytes()); + MultipartFile file = new MockMultipartFile(TEST, is); + ScenarioInput scenarioInput = new ScenarioInput(); + Scenario scenario = new Scenario(); + given(scenarioResponseEntity.getBody()).willReturn(scenario); + given(client.scenarioUpdateById(any(), any(), any())).willThrow(FeignException.class); + UUID uuid = UUID.randomUUID(); + assertThrows(FeignException.class, () -> scenarioRestClient.updateScenario(uuid, scenarioInput, file)); + } + + @Test + void scenarioReadByQuery() { + ScenarioPage scenarioPage = new ScenarioPage(); + given(scenarioPageResponseEntity.getBody()).willReturn(scenarioPage); + given(client.scenarioReadByQuery(any(), any(), any(), any(), any())).willReturn(scenarioPageResponseEntity); + assertSame(scenarioPage, scenarioRestClient.scenarioReadByQuery(null, null, 0, 10, null)); + verify(client).scenarioReadByQuery(any(), any(), any(), any(), any()); + verify(scenarioPageResponseEntity).getBody(); + } + + @Test + void searchScenarioByPattern() { + ScenarioPage scenarioPage = new ScenarioPage(); + given(scenarioPageResponseEntity.getBody()).willReturn(scenarioPage); + given(client.searchScenarioByPattern(anyString(), anyInt(), anyInt())).willReturn(scenarioPageResponseEntity); + assertSame(scenarioPage, scenarioRestClient.searchScenarioByPattern(TEST, 0, 10)); + verify(client).searchScenarioByPattern(anyString(), anyInt(), anyInt()); + verify(scenarioPageResponseEntity).getBody(); + } + + @Test + void searchScenarioByPatternWithException() { + given(scenarioPageResponseEntity.getBody()).willThrow(FeignException.InternalServerError.class); + given(client.searchScenarioByPattern(anyString(), anyInt(), anyInt())).willReturn(scenarioPageResponseEntity); + assertThrows(FeignException.InternalServerError.class, () -> scenarioRestClient.searchScenarioByPattern(TEST, 0, 10)); + } +} diff --git a/dco-gateway/app/src/test/java/com/tsystems/dco/scenario/ScenarioRestTest.java b/dco-gateway/app/src/test/java/com/tsystems/dco/scenario/ScenarioRestTest.java new file mode 100644 index 0000000000000000000000000000000000000000..849b01849b6bc8d0db04e354739e36f8a86d9cd5 --- /dev/null +++ b/dco-gateway/app/src/test/java/com/tsystems/dco/scenario/ScenarioRestTest.java @@ -0,0 +1,76 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.scenario; + +import com.tsystems.dco.scenario.model.Scenario; +import com.tsystems.dco.scenario.model.ScenarioInput; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.UUID; + +import static org.mockito.BDDMockito.any; +import static org.mockito.BDDMockito.given; + +@ExtendWith(MockitoExtension.class) +class ScenarioRestTest { + + @InjectMocks + private ScenarioController scenarioController; + @Mock + private ScenarioRestClient scenarioRestClient; + private final static String TEST = "Test"; + + @Test + void createScenario() throws IOException { + UUID id = UUID.randomUUID(); + Scenario scenario = Scenario.builder().id(id).build(); + given(scenarioRestClient.createScenario(any(), any())).willReturn(scenario); + ScenarioInput scenarioInput = new ScenarioInput(); + Assertions.assertEquals(id, scenarioController.createScenario(getFile(), scenarioInput)); + } + + @Test + void updateScenario() throws IOException { + UUID id = UUID.randomUUID(); + Scenario scenario = Scenario.builder().id(id).build(); + given(scenarioRestClient.updateScenario(any(), any(), any())).willReturn(scenario); + ScenarioInput scenarioInput = new ScenarioInput(); + Assertions.assertEquals(id, scenarioController.updateScenario(id, getFile(), scenarioInput)); + } + + private MultipartFile getFile() throws IOException { + InputStream is = new ByteArrayInputStream(TEST.getBytes()); + return new MockMultipartFile(TEST, is); + } +} diff --git a/dco-gateway/app/src/test/java/com/tsystems/dco/scenario/SimulationControllerTest.java b/dco-gateway/app/src/test/java/com/tsystems/dco/scenario/SimulationControllerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..499060681d92466f206dc95d3810eeafc7feb30d --- /dev/null +++ b/dco-gateway/app/src/test/java/com/tsystems/dco/scenario/SimulationControllerTest.java @@ -0,0 +1,90 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.scenario; + +import com.tsystems.dco.scenario.model.SimulationPage; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.graphql.tester.AutoConfigureGraphQlTester; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.graphql.test.tester.GraphQlTester; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.when; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) +@AutoConfigureGraphQlTester +class SimulationControllerTest { + + @Autowired + private GraphQlTester tester; + + @MockBean + private SimulationRestClient client; + + @Test + void launchSimulation() { + var track = """ + mutation LAUNCH_SIMULATION { + launchSimulation( + simulationInput: { + name: "Test Simulation" + environment: "Development Demo" + platform: "Task Management" + scenarioType: "Over-The-Air Service" + hardware: "TEST_OTA" + description: "Test Simulation Data" + tracks: ["bd6ed64b-0547-42b4-9769-d0a0ea095070", "5a955942-6169-4b0f-a26b-b4585d19ed55"] + scenarios: ["74fef2c0-4efc-42bb-b064-da7371421c8b"] + createdBy: "Kashif.Kamal" + } + ) + } + """; + when(client.launchSimulation(any())).thenReturn("test"); + tester.document(track).execute().errors().satisfy(error -> assertThat(error.size() == 0)); + } + + @Test + void simulationReadByQuery() { + var track = """ + query LIST_SIMULATION { + simulationReadByQuery(search: null, query: null, page: 0, size: 2, sort: null){ + content{ + id + name + status + environment + } + total + } + } + """; + when(client.simulationReadByQuery(any(), any(), anyInt(), anyInt(), any())).thenReturn(new SimulationPage()); + tester.document(track).execute().errors().satisfy(error -> assertThat(error.size() == 0)); + } +} diff --git a/dco-gateway/app/src/test/java/com/tsystems/dco/scenario/SimulationRestClientTest.java b/dco-gateway/app/src/test/java/com/tsystems/dco/scenario/SimulationRestClientTest.java new file mode 100644 index 0000000000000000000000000000000000000000..f3567222918983e76ad3a1f2b42a15f9a1861f1d --- /dev/null +++ b/dco-gateway/app/src/test/java/com/tsystems/dco/scenario/SimulationRestClientTest.java @@ -0,0 +1,76 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.scenario; + +import com.tsystems.dco.scenario.feign.ScenarioFeignClient; +import com.tsystems.dco.scenario.model.SimulationInput; +import com.tsystems.dco.scenario.model.SimulationPage; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.BDDMockito.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; + +@ContextConfiguration(classes = {SimulationRestClient.class}) +@ExtendWith(SpringExtension.class) +class SimulationRestClientTest { + + @Autowired + private SimulationRestClient simulationRestClient; + @MockBean + private ScenarioFeignClient client; + @Mock + private ResponseEntity<SimulationPage> simulationPageResponseEntity; + @Mock + private ResponseEntity<String> responseEntity; + + + @Test + void launchSimulation() { + SimulationInput simulationInput = new SimulationInput(); + given(responseEntity.getBody()).willReturn("test"); + given(client.launchSimulation(any())).willReturn(responseEntity); + assertSame("test", simulationRestClient.launchSimulation(new SimulationInput())); + verify(client).launchSimulation(any()); + verify(responseEntity).getBody(); + } + + @Test + void simulationReadByQuery() { + SimulationPage simulationPage = new SimulationPage(); + given(simulationPageResponseEntity.getBody()).willReturn(simulationPage); + given(client.simulationReadByQuery(any(), any(), any(), any(), any())).willReturn(simulationPageResponseEntity); + assertSame(simulationPage, simulationRestClient.simulationReadByQuery(null, null, 0, 10, null)); + verify(client).simulationReadByQuery(any(), any(), any(), any(), any()); + verify(simulationPageResponseEntity).getBody(); + } +} diff --git a/dco-gateway/app/src/test/java/com/tsystems/dco/track/TrackControllerTest.java b/dco-gateway/app/src/test/java/com/tsystems/dco/track/TrackControllerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..faae6482aeb81f072883eb1cc6d5f23191e22455 --- /dev/null +++ b/dco-gateway/app/src/test/java/com/tsystems/dco/track/TrackControllerTest.java @@ -0,0 +1,228 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.track; + +import com.tsystems.dco.exception.BaseException; +import com.tsystems.dco.track.model.Track; +import com.tsystems.dco.track.model.TrackPage; +import com.tsystems.dco.track.model.VehiclePage; +import com.tsystems.dco.track.model.VehicleResponse; +import org.junit.jupiter.api.Test; + +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.anyString; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.graphql.tester.AutoConfigureGraphQlTester; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.graphql.test.tester.GraphQlTester; +import org.springframework.http.HttpStatus; + +import java.util.ArrayList; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) +@AutoConfigureGraphQlTester +class TrackControllerTest { + + @Autowired + private GraphQlTester tester; + + @MockBean + private TrackRestClient client; + + @Test + void createTrack() { + var track = """ + mutation CREATE_TRACK { + createTrack ( + trackInput: { + name: "Test Track" + trackType: "Test" + duration: "7" + state: "CREATED" + description: "Test Desc" + vehicles: [ + { + vin: "VINTEST1HQMIOUT08" + country: "DE" + } + ] + } + ){ + id + } + } + """; + when(client.createTrack(any())).thenReturn(new Track()); + tester.document(track).execute().errors().satisfy(error -> assertThat(error.size() == 0)); + } + + + @Test + void trackReadByQuery() { + var tracks = """ + query LIST_TRACK { + trackReadByQuery(search: null, query: null, page: 0, size: 2, sort: null){ + content{ + id + name + state + trackType + } + total + } + } + """; + when(client.trackReadByQuery(any(), any(), anyInt(), anyInt(), any())).thenReturn(new TrackPage()); + tester.document(tracks).execute().errors().satisfy(error -> assertThat(error.size() == 0)); + } + + @Test + void deleteTrackById() { + var track = """ + mutation DELETE_TRACK { + deleteTrackById(id : "e8e996c5-8081-43c9-9d97-cb170eb0eee5") + } + """; + when(client.deleteTrackById(any())).thenReturn("test"); + tester.document(track).execute().errors().satisfy(error -> assertThat(error.size() == 0)); + } + + @Test + void findTrackById() { + var track = """ + query TRACK_BY_ID { + findTrackById(id : "818094c5-0be4-4a0d-bf9f-0c70919d05ee") { + id + name + vehicles{ + vin + devices { + id + type + status + } + } + } + } + """; + when(client.findTrackById(any())).thenReturn(new Track()); + tester.document(track).execute().errors().satisfy(error -> assertThat(error.size() == 0)); + } + + @Test + void searchTrackByPattern() { + var tracks = """ + query SEARCH_TRACK { + searchTrackByPattern(trackPattern: "eu", page:0, size:11){ + content{ + id + name + } + total + } + } + + """; + when(client.searchTrackByPattern(anyString(), anyInt(), anyInt())).thenReturn(new TrackPage()); + tester.document(tracks).execute().errors().satisfy(error -> assertThat(error.size() == 0)); + } + + @Test + void vehicleReadByQuery() { + var vehicles = """ + query LIST_VEHICLE { + vehicleReadByQuery(search: null, query: null, page: 0, size: 2, sort: null){ + content{ + vin + status + type + fleets { + id + name + type + } + } + total + } + } + + """; + when(client.vehicleReadByQuery(any(), any(), anyInt(), anyInt(), any())).thenReturn(new VehiclePage()); + tester.document(vehicles).execute().errors().satisfy(error -> assertThat(error.size() == 0)); + } + + @Test + void findTrackByIdWithError() { + var track = """ + query TRACK_BY_ID { + findTrackById(id : "818094c5-0be4-4a0d-bf9f-0c70919d05ee") { + id + name + vehicles{ + vin + devices { + id + type + status + } + } + } + } + """; + when(client.findTrackById(any())).thenThrow(new BaseException(HttpStatus.NOT_FOUND, "[{\"message\":\"Track with id 818094c5-0be4-4a0d-bf9f-0c70919d05ee not found.\",\"status\":\"NOT_FOUND\"}]")); + tester.document(track).execute().errors().satisfy(error -> assertThat(error.size() == 0)); + } + + @Test + void getHardwareModule() { + var hardware = """ + query GET_HARDWARE_MODULE { + getHardwareModule + } + """; + when(client.getHardwareModule()).thenReturn(new ArrayList<>()); + tester.document(hardware).execute().errors().satisfy(error -> assertThat(error.size() == 0)); + } + + @Test + void getVehicleByVin() { + var vehicle = """ + query VEHICLE_BY_VIN{ + getVehicleByVin(vin: "BBTEST00000000340"){ + vin + model + brand + } + } + """; + when(client.getVehicleByVin(anyString())).thenReturn(new VehicleResponse()); + tester.document(vehicle).execute().errors().satisfy(error -> assertThat(error.size() == 0)); + } +} diff --git a/dco-gateway/app/src/test/java/com/tsystems/dco/track/TrackRestClientTest.java b/dco-gateway/app/src/test/java/com/tsystems/dco/track/TrackRestClientTest.java new file mode 100644 index 0000000000000000000000000000000000000000..da4091b841cd11a468110e89ead83c6abb1de7da --- /dev/null +++ b/dco-gateway/app/src/test/java/com/tsystems/dco/track/TrackRestClientTest.java @@ -0,0 +1,149 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.track; + +import com.tsystems.dco.track.feign.TrackFeignClient; +import com.tsystems.dco.track.model.*; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.BDDMockito.*; +import static org.mockito.Mockito.verify; + +@ContextConfiguration(classes = {TrackRestClient.class}) +@ExtendWith(SpringExtension.class) +class TrackRestClientTest { + + @Autowired + private TrackRestClient trackRestClient; + @MockBean + private TrackFeignClient client; + @Mock + private ResponseEntity<TrackPage> trackPageResponseEntity; + @Mock + private ResponseEntity<String> responseEntity; + @Mock + private ResponseEntity<Track> trackResponseEntity; + @Mock + private ResponseEntity<VehiclePage> vehiclePageResponseEntity; + @Mock + private ResponseEntity<List<String>> listResponseEntity; + @Mock + private ResponseEntity<VehicleResponse> vehicleResponseResponseEntity; + + private final static String TEST = "Test"; + + @Test + void createTrack() { + Track track = new Track(); + given(trackResponseEntity.getBody()).willReturn(track); + given(client.createTrack(any())).willReturn(trackResponseEntity); + assertSame(track, trackRestClient.createTrack(new TrackInput())); + verify(client).createTrack(any()); + verify(trackResponseEntity).getBody(); + } + + + @Test + void deleteTrackById() { + given(responseEntity.getBody()).willReturn(TEST); + given(client.deleteTrackById(any())).willReturn(responseEntity); + assertSame(TEST, trackRestClient.deleteTrackById(UUID.randomUUID())); + verify(client).deleteTrackById(any()); + verify(responseEntity).getBody(); + } + + @Test + void findTrackById() { + Track track = new Track(); + given(trackResponseEntity.getBody()).willReturn(track); + given(client.findTrackById(any())).willReturn(trackResponseEntity); + assertSame(track, trackRestClient.findTrackById(UUID.randomUUID())); + verify(client).findTrackById(any()); + verify(trackResponseEntity).getBody(); + + } + + @Test + void searchTrackByPattern() { + TrackPage trackPage = new TrackPage(); + given(trackPageResponseEntity.getBody()).willReturn(trackPage); + given(client.searchTrackByPattern(anyString(), anyInt(), anyInt())).willReturn(trackPageResponseEntity); + assertSame(trackPage, trackRestClient.searchTrackByPattern(TEST, 0, 10)); + verify(client).searchTrackByPattern(anyString(), anyInt(), anyInt()); + verify(trackPageResponseEntity).getBody(); + } + + @Test + void trackReadByQuery() { + TrackPage trackPage = new TrackPage(); + given(trackPageResponseEntity.getBody()).willReturn(trackPage); + given(client.trackReadByQuery(any(), any(), any(), any(), any())).willReturn(trackPageResponseEntity); + trackRestClient.trackReadByQuery(null, null, 0, 10, null); + verify(client).trackReadByQuery(any(), any(), any(), any(), any()); + verify(trackPageResponseEntity).getBody(); + } + + @Test + void vehicleReadByQuery() { + VehiclePage vehiclePage = new VehiclePage(); + given(vehiclePageResponseEntity.getBody()).willReturn(vehiclePage); + given(client.vehicleReadByQuery(any(), any(), any(), any(), any())).willReturn(vehiclePageResponseEntity); + assertSame(vehiclePage, trackRestClient.vehicleReadByQuery(null, null, 0, 10, null)); + verify(client).vehicleReadByQuery(any(), any(), any(), any(), any()); + verify(vehiclePageResponseEntity).getBody(); + } + + @Test + void getHardwareModule() { + List<String> list = new ArrayList<>(); + list.add(TEST); + given(listResponseEntity.getBody()).willReturn(list); + given(client.getHardwareModule()).willReturn(listResponseEntity); + assertSame(list, trackRestClient.getHardwareModule()); + verify(client).getHardwareModule(); + verify(listResponseEntity).getBody(); + } + + @Test + void getVehicleByVin() { + VehicleResponse vehicleResponse = new VehicleResponse(); + given(vehicleResponseResponseEntity.getBody()).willReturn(vehicleResponse); + given(client.getVehicleByVin(anyString())).willReturn(vehicleResponseResponseEntity); + assertSame(vehicleResponse, trackRestClient.getVehicleByVin(TEST)); + verify(client).getVehicleByVin(anyString()); + verify(vehicleResponseResponseEntity).getBody(); + } +} diff --git a/dco-gateway/pom.xml b/dco-gateway/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..f408438e4873a4415a88205072a597fb7bc151a5 --- /dev/null +++ b/dco-gateway/pom.xml @@ -0,0 +1,217 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <groupId>com.tsystems.dco</groupId> + <artifactId>dco-gateway</artifactId> + <packaging>pom</packaging> + <version>latest</version> + <modules> + <module>api</module> + <module>app</module> + </modules> + + <organization> + <name>T-Systems International GmbH</name> + <url>https://t-systems.com</url> + </organization> + + <developers> + <developer> + <name>T-Systems</name> + <email>info@t-systems.com</email> + <organization>T-Systems International GmbH</organization> + <organizationUrl>https://t-systems.com</organizationUrl> + </developer> + </developers> + + <properties> + <maven.compiler.source>17</maven.compiler.source> + <maven.compiler.target>17</maven.compiler.target> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <!-- charset --> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> + <spring.boot.version>3.1.0</spring.boot.version> + <spring.cloud.version>2022.0.3</spring.cloud.version> + </properties> + + <dependencyManagement> + <dependencies> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-dependencies</artifactId> + <version>${spring.boot.version}</version> + <type>pom</type> + <scope>import</scope> + </dependency> + <dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-dependencies</artifactId> + <version>${spring.cloud.version}</version> + <type>pom</type> + <scope>import</scope> + </dependency> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + <version>1.18.24</version> + </dependency> + <dependency> + <groupId>org.mapstruct</groupId> + <artifactId>mapstruct</artifactId> + <version>1.4.2.Final</version> + </dependency> + <dependency> + <groupId>org.mapstruct</groupId> + <artifactId>mapstruct-processor</artifactId> + <version>1.4.2.Final</version> + </dependency> + <dependency> + <groupId>org.springdoc</groupId> + <artifactId>springdoc-openapi-ui</artifactId> + <version>1.7.0</version> + </dependency> + </dependencies> + </dependencyManagement> + + <build> + <pluginManagement> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-resources-plugin</artifactId> + <version>3.2.0</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.8.1</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <version>3.0.0-M5</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-checkstyle-plugin</artifactId> + <version>3.1.2</version> + <dependencies> + <dependency> + <groupId>com.puppycrawl.tools</groupId> + <artifactId>checkstyle</artifactId> + <version>10.1</version> + </dependency> + </dependencies> + </plugin> + <plugin> + <groupId>org.openapitools</groupId> + <artifactId>openapi-generator-maven-plugin</artifactId> + <version>6.6.0</version> + </plugin> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + <version>${spring.boot.version}</version> + </plugin> + <plugin> + <groupId>com.github.spotbugs</groupId> + <artifactId>spotbugs-maven-plugin</artifactId> + <version>4.6.0.0</version> + </plugin> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + <version>0.8.8</version> + </plugin> + <plugin> + <groupId>org.sonarsource.scanner.maven</groupId> + <artifactId>sonar-maven-plugin</artifactId> + <version>3.9.1.2184</version> + </plugin> + <plugin> + <groupId>org.cyclonedx</groupId> + <artifactId>cyclonedx-maven-plugin</artifactId> + <version>2.7.0</version> + </plugin> + <plugin> + <groupId>io.github.pmckeown</groupId> + <artifactId>dependency-track-maven-plugin</artifactId> + <version>1.1.3</version> + </plugin> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + <executions> + <execution> + <id>prepare-agent</id> + <goals> + <goal>prepare-agent</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>io.github.pmckeown</groupId> + <artifactId>dependency-track-maven-plugin</artifactId> + <version>1.1.3</version> + </plugin> + </plugins> + </pluginManagement> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-resources-plugin</artifactId> + <configuration> + <delimiters> + <delimiter>@</delimiter> + </delimiters> + <useDefaultDelimiters>false</useDefaultDelimiters> + </configuration> + </plugin> + <plugin> + <groupId>org.cyclonedx</groupId> + <artifactId>cyclonedx-maven-plugin</artifactId> + <executions> + <execution> + <id>aggregate</id> + <phase>package</phase> + <goals> + <goal>makeAggregateBom</goal> + </goals> + </execution> + </executions> + <configuration> + <projectType>application</projectType> + <schemaVersion>1.4</schemaVersion> + <includeBomSerialNumber>true</includeBomSerialNumber> + <includeCompileScope>true</includeCompileScope> + <includeRuntimeScope>true</includeRuntimeScope> + <includeProvidedScope>false</includeProvidedScope> + <includeSystemScope>true</includeSystemScope> + <includeTestScope>false</includeTestScope> + <includeLicenseText>false</includeLicenseText> + <outputReactorProjects>true</outputReactorProjects> + <outputFormat>all</outputFormat> + <outputName>bom</outputName> + </configuration> + </plugin> + <plugin> + <groupId>io.github.pmckeown</groupId> + <artifactId>dependency-track-maven-plugin</artifactId> + <version>1.1.3</version> + <configuration> + <projectName>${app.name}</projectName> + <projectVersion>latest</projectVersion> + </configuration> + </plugin> + </plugins> + </build> +</project> diff --git a/dco-gateway/settings.xml b/dco-gateway/settings.xml new file mode 100644 index 0000000000000000000000000000000000000000..0f70d3d49a0624721d6ee009d20d64187604bd25 --- /dev/null +++ b/dco-gateway/settings.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<settings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/SETTINGS/1.0.0" + xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd"> + <localRepository>${HOME}/.m2/repository</localRepository> + <interactiveMode>false</interactiveMode> + <offline/> + <pluginGroups/> + <servers/> + <mirrors/> + <proxies/> + <profiles/> + <activeProfiles/> +</settings> diff --git a/developer-console-ui/.gitattributes b/developer-console-ui/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..2dc12354f21485a366e360c1f2a8cc7f60936353 --- /dev/null +++ b/developer-console-ui/.gitattributes @@ -0,0 +1,26 @@ +* text=auto eol=lf + +*.java text diff=java +*.gradle text diff=groovy +*.gradle.kts text diff=groovy +*.css text diff=css +*.df text +*.htm text diff=html +*.html text diff=html +*.js text +*.jsp text +*.jspf text +*.jspx text +*.properties text +*.tld text +*.tag text +*.tagx text +*.xml text + +*.class binary +*.dll binary +*.ear binary +*.jar binary +*.so binary +*.war binary +*.jks binary diff --git a/developer-console-ui/.gitignore b/developer-console-ui/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..aa22a2f5dc81b611276f42d17e0230dde549cd46 --- /dev/null +++ b/developer-console-ui/.gitignore @@ -0,0 +1,55 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/** +!**/src/test/** + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ + +### VS Code ### +.vscode/ + +### Helm ### +chart/Chart.lock +chart/charts + +### NPM ### +**/node_modules + +### Bash ### +.bashenv-* +.aws +.cache +.config +.helm +.kube +.npm +.yarn +.trivy +tmp +artifacts + + +**/yarn.lock +**/package-lock.json \ No newline at end of file diff --git a/developer-console-ui/Dockerfile b/developer-console-ui/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..43b993b4b84044a333903e5329a671dbf238e9f5 --- /dev/null +++ b/developer-console-ui/Dockerfile @@ -0,0 +1,11 @@ +FROM node:16.14-alpine AS app +RUN addgroup -g 1001 -S nodejs +RUN adduser -u 1001 -S nextjs +WORKDIR /app +COPY --chown=nextjs:nodejs developer-console-ui/app/next.config.js ./next.config.js +COPY --chown=nextjs:nodejs developer-console-ui/app/public ./public +COPY --chown=nextjs:nodejs developer-console-ui/app/.next ./.next +COPY --chown=nextjs:nodejs developer-console-ui/app/node_modules ./node_modules +COPY --chown=nextjs:nodejs developer-console-ui/app/package.json ./package.json +USER nextjs +CMD ["yarn", "start"] \ No newline at end of file diff --git a/developer-console-ui/app/.env b/developer-console-ui/app/.env new file mode 100644 index 0000000000000000000000000000000000000000..ba65b5b447db5f26dd7ab81800e2e3303b5ae507 --- /dev/null +++ b/developer-console-ui/app/.env @@ -0,0 +1,2 @@ +APP_DCO_GATEWAY_SERVICE_URL=http://localhost:8080 +NEXT_PUBLIC_FILE=http://minio:9000/dco-scenario-library-service/scenario/fe7f4c94-c21a-4a4b-a3fc-c1b309b0f5f0/files/software_package.txt \ No newline at end of file diff --git a/developer-console-ui/app/.eslintignore b/developer-console-ui/app/.eslintignore new file mode 100644 index 0000000000000000000000000000000000000000..50ad4e50d5df464a1a3f6652fcbe4e10fe34c3c9 --- /dev/null +++ b/developer-console-ui/app/.eslintignore @@ -0,0 +1,3 @@ +.next +node_modules +dist diff --git a/developer-console-ui/app/.eslintrc.json b/developer-console-ui/app/.eslintrc.json new file mode 100644 index 0000000000000000000000000000000000000000..bffb357a7122523ec94045523758c4b825b448ef --- /dev/null +++ b/developer-console-ui/app/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/developer-console-ui/app/.gitignore b/developer-console-ui/app/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..912f3ba737cd358ce28c6895a6ecb37b1c6e25fa --- /dev/null +++ b/developer-console-ui/app/.gitignore @@ -0,0 +1,36 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env.local +.scannerwork + bom.xml +.env.development.local +.env.test.local +.env.production.local + +# vercel +.vercel diff --git a/developer-console-ui/app/.prettierignore b/developer-console-ui/app/.prettierignore new file mode 100644 index 0000000000000000000000000000000000000000..3397a217616ce17929286236996e59817a0adc7c --- /dev/null +++ b/developer-console-ui/app/.prettierignore @@ -0,0 +1,4 @@ +.next +node_modules +public +styles diff --git a/developer-console-ui/app/__tests__/Layout.test.tsx b/developer-console-ui/app/__tests__/Layout.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..48d3447c85a9ad0680c52d503af1e1b6e5b1e5fe --- /dev/null +++ b/developer-console-ui/app/__tests__/Layout.test.tsx @@ -0,0 +1,122 @@ +import '@testing-library/jest-dom'; +import { cleanup, fireEvent, render, screen } from '@testing-library/react'; +import { StoreProvider } from 'easy-peasy'; +import { MemoryRouterProvider } from 'next-router-mock/dist/MemoryRouterProvider'; +import React from 'react'; +import Layout from '../components/layout/layout'; +import { store } from '../services/store.service'; + +describe('<Layout>', () => { + const useRouter = jest.spyOn(require('next/router'), 'useRouter'); + useRouter.mockImplementation(() => ({ + pathname: '/dco', + push: jest.fn() + })); + afterEach(cleanup); + it("should render index", () => { + render( + // @ts-ignore + // @ts-ignore + <StoreProvider store={store}> + <MemoryRouterProvider url="/dco"> + <Layout /> + </MemoryRouterProvider> + </StoreProvider>); + store.getActions().setInvert(true); + store.getActions().setUser(true); + }); + + it("should fire an event on button click", () => { + useRouter + render( + // @ts-ignore + // @ts-ignore + <StoreProvider store={store}> + <MemoryRouterProvider url="/dco"> + <Layout /> + </MemoryRouterProvider> + </StoreProvider>); + store.getActions().setInvert(true); + const logoInHeader = screen.getByTestId('logo'); + fireEvent.click(logoInHeader) + store.getActions().setUser(true); + + }); + it("should fire an event on check click", () => { + render( + // @ts-ignore + // @ts-ignore + <StoreProvider store={store}> <MemoryRouterProvider url="/dco"> + <Layout /> + </MemoryRouterProvider> + </StoreProvider>); + store.getActions().setUser(true); + + }); + it("check if logo is there", () => { + render( + // @ts-ignore + // @ts-ignore + <StoreProvider store={store}> <MemoryRouterProvider url="/dco"> + <Layout /> + </MemoryRouterProvider> + </StoreProvider>); + const logoInHeader = screen.getAllByTestId('logo'); + expect(logoInHeader.length).toBe(1); + store.getActions().setUser(true); + + }); + it("check if logo is there2", () => { + render( + // @ts-ignore + // @ts-ignore + <StoreProvider store={store}> + <MemoryRouterProvider url="/scenario"> + <Layout /> + </MemoryRouterProvider> + </StoreProvider>); + const logoInHeader = screen.getAllByTestId('logo'); + expect(logoInHeader.length).toBe(1); + store.getActions().setUser(true); + + }); + // test('checks if logo rendered', () => { + + // }); + // it("should handle if else", () => { + // render( <MemoryRouterProvider url="/dco"> + // <Layout /> + // </MemoryRouterProvider>); + // const btnClick = screen.getByTestId("logout"); + // fireEvent.click(btnClick); + // }); + // customHeaderRender(<Layout />); + + // const children = screen.getByText('children'); + + // test('checks if children rendered', () => { + // expect(children).toBeDefined(); + // }); + + // test('checks if vehicles nav link is active', () => { + // expect(within(header).getByTestId('header-active-link').textContent).toBe('vehicles'); + // }); + + // test('checks if displays authenticated user name', () => { + // expect(within(header).getByTestId('header-user-name').textContent).toBe('test_user'); + // }); + + // test('checks if header menu rendered', () => { + // const menuInHeader = within(header).getAllByTestId('header-menu'); + + // expect(menuInHeader.length).toBe(1); + // }); + + // test('checks if navigation rendered', () => { + // const navigationInHeader = within(header).getAllByTestId('header-navigation'); + + // expect(navigationInHeader.length).toBe(1); + // }); + + +}); diff --git a/developer-console-ui/app/__tests__/addEditScneario/menuForScenario.test.tsx b/developer-console-ui/app/__tests__/addEditScneario/menuForScenario.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ad4274008855b1fdc4ff554efafa6db6b44092fd --- /dev/null +++ b/developer-console-ui/app/__tests__/addEditScneario/menuForScenario.test.tsx @@ -0,0 +1,78 @@ +import { fireEvent, render, screen } from '@testing-library/react'; +import { StoreProvider } from 'easy-peasy'; +import { store } from '../../services/store.service'; +import { enableFetchMocks } from 'jest-fetch-mock' +enableFetchMocks() +jest.mock('next/router', () => require('next-router-mock')); +import fetch from 'jest-fetch-mock' +import MenuForScenario from '../../pages/dco/addEditScenario/menuForScenario'; +import { MemoryRouterProvider } from 'next-router-mock/dist/MemoryRouterProvider'; +import { MockedProvider } from '@apollo/react-testing'; +import { DELETE_SCENARIO } from '../../services/functionScenario.services'; + +describe('MenuForScenario', () => { + const mockPerformanceMark = jest.fn(); + window.performance.mark = mockPerformanceMark; + jest.useFakeTimers(); + beforeEach(() => { + fetch.resetMocks(); + }); + const props = { + variant: "naked", + cellData: { + check: "", + filename: "scenarioLib.txt", + lastUpdated: "2023-03-29, 4:35:08 p.m.", + menu: "", + scenario: "scenario 1", + sid: "bcbb76e2-650f-4ce0-a7da-b3082a58b8f3", + type: "MQTT" + }, + insideTrack: undefined, setSuccessMsgScenario: () => { }, setToastOpenScenario: () => { } + + } + const props2 = { + variant: "naked", + cellData: {}, + insideTrack: undefined, setSuccessMsgScenario: () => { }, setToastOpenScenario: () => { } + } + const mockList = [{ + request: { + query: DELETE_SCENARIO, + variables: { id: '' } + }, + result: { data: { deleteTrack: "Track deleted" } }, + }] + + test('delete scenario render', () => { + render( + // @ts-ignore + <StoreProvider store={store}> + <MemoryRouterProvider url="/dco/scenario"> + <MockedProvider mocks={mockList}> + <MenuForScenario {...props} /> + </MockedProvider> + </MemoryRouterProvider> + </StoreProvider> + ) + const checkBtn = screen.getByTestId("btn"); + fireEvent.click(checkBtn); + + }); + + test('delete scenario render', () => { + render( + // @ts-ignore + <StoreProvider store={store}> + <MemoryRouterProvider url="/dco/scenario"> + <MockedProvider mocks={mockList}> + <MenuForScenario {...props2} /> + </MockedProvider> + </MemoryRouterProvider> + </StoreProvider> + ) + const checkBtn = screen.getByTestId("btn"); + fireEvent.click(checkBtn); + + }); +}) \ No newline at end of file diff --git a/developer-console-ui/app/__tests__/addEditScneario/newScenario.test.tsx b/developer-console-ui/app/__tests__/addEditScneario/newScenario.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..26acab43a6f8e7ef1a623af0c6eb929ddf459f4e --- /dev/null +++ b/developer-console-ui/app/__tests__/addEditScneario/newScenario.test.tsx @@ -0,0 +1,62 @@ +import { act, fireEvent, render,screen } from '@testing-library/react'; +import { StoreProvider } from 'easy-peasy'; +import { MockedProvider } from "@apollo/react-testing"; +import { store } from '../../services/store.service'; +import { MemoryRouterProvider } from 'next-router-mock/dist/MemoryRouterProvider'; +import { enableFetchMocks } from 'jest-fetch-mock' +enableFetchMocks() +jest.mock('next/router', () => require('next-router-mock')); +import fetch from 'jest-fetch-mock' +import NewScenario from '../../pages/dco/addEditScenario/newScenario'; + +describe('Table render in scenario', () => { + const props = { show: true, onClose: jest.fn(), path: 'create', cellData: { scenario: 'scenario', description: 'test', type: 'MQTT', sid: '12344', filename: 'test.txt' }, setToastOpenScenario: jest.fn(), setSuccessMsgScenario: jest.fn() } + + const props2 = { show: true, onClose: jest.fn(), path: 'update', cellData: { scenario: 'scenario', description: 'test', type: 'CAN', sid: '12344', filename: 'test.txt' }, setToastOpenScenario: jest.fn(), setSuccessMsgScenario: jest.fn() } + const useRouter = jest.spyOn(require('next/router'), 'useRouter'); + useRouter.mockImplementation(() => ({ + pathname: '/dco/scenario', + })); + test('table with props', async () => { + store.getActions().setCount(0); + useRouter + render( + // @ts-ignore + <StoreProvider store={store}> + <MemoryRouterProvider url="/dco/scenario"> + <MockedProvider addTypename={false}> + <NewScenario {...props} /> + </MockedProvider> + </MemoryRouterProvider> + </StoreProvider> + ) + const checkBtn = screen.getByTestId("btn1"); + fireEvent.click(checkBtn); + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 0)); + }); + }); + test('table with props', async () => { + store.getActions().setCount(0); + useRouter + render( + // @ts-ignore + <StoreProvider store={store}> + <MemoryRouterProvider url="/dco/scenario"> + <MockedProvider addTypename={false}> + <NewScenario {...props2} /> + </MockedProvider> + </MemoryRouterProvider> + </StoreProvider> + ) + // const checkBtn = screen.getByTestId("btn2"); + // fireEvent.click(checkBtn); + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 0)); + }); + }); + beforeEach(() => { + fetch.resetMocks(); + }); + +}) diff --git a/developer-console-ui/app/__tests__/addSimulation/addSimulation.test.tsx b/developer-console-ui/app/__tests__/addSimulation/addSimulation.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e4702b1d2d28dd84080104087ef602f78d2eb936 --- /dev/null +++ b/developer-console-ui/app/__tests__/addSimulation/addSimulation.test.tsx @@ -0,0 +1,84 @@ +import { act, fireEvent, render, screen } from '@testing-library/react'; +import { StoreProvider } from 'easy-peasy'; +import { MockedProvider } from "@apollo/react-testing"; +import { store } from '../../services/store.service'; +import { MemoryRouterProvider } from 'next-router-mock/dist/MemoryRouterProvider'; +import { enableFetchMocks } from 'jest-fetch-mock' +enableFetchMocks() +jest.mock('next/router', () => require('next-router-mock')); +import fetch from 'jest-fetch-mock' +import AddSimulation from '../../pages/dco/addSimulation'; +import Simulation from '../../pages/dco/simulation'; +import { GET_SIMULATIONS } from '../../services/queries'; +import { gql } from '@apollo/client'; + +describe('Table render in scenario', () => { + const useRouter = jest.spyOn(require('next/router'), 'useRouter'); + useRouter.mockImplementation(() => ({ + pathname: '/dco/simulation', + })); + const mockList = [{ + request: { + query: gql(GET_SIMULATIONS), + variables: { + search: null, + query: null, + page: 1, + size: 10, + sort: null, + } + }, + result: { data: { simulationReadByQuery: { pages: 1, total: 2 } } }, + }] + test('table with props backbutton', async () => { + store.getActions().setCount(0); + useRouter + render( + // @ts-ignore + <StoreProvider store={store}> + <MemoryRouterProvider url="/dco/addSimulation"> + <MockedProvider addTypename={false}> + <AddSimulation /> + </MockedProvider> + </MemoryRouterProvider> + </StoreProvider> + ) + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 0)); + }); + }); + test('table with props', async () => { + store.getActions().setCount(0); + useRouter + render( + // @ts-ignore + <StoreProvider store={store}> + <MemoryRouterProvider url="/dco/addSimulation"> + <MockedProvider addTypename={false}> + <AddSimulation /> + </MockedProvider> + </MemoryRouterProvider> + </StoreProvider> + ) + const checkBtn = screen.getByTestId("backButton"); + fireEvent.click(checkBtn); + useRouter + render( + // @ts-ignore + <StoreProvider store={store}> + <MemoryRouterProvider url="/dco/simulation"> + <MockedProvider mocks={mockList}> + <Simulation /> + </MockedProvider> + </MemoryRouterProvider> + </StoreProvider> + ) + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 0)); + }); + }); + beforeEach(() => { + fetch.resetMocks(); + }); + +}) diff --git a/developer-console-ui/app/__tests__/addTrack/addTrack.test.tsx b/developer-console-ui/app/__tests__/addTrack/addTrack.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..68b11844dd2b8c7be038a96ba21cbede4f5fc600 --- /dev/null +++ b/developer-console-ui/app/__tests__/addTrack/addTrack.test.tsx @@ -0,0 +1,637 @@ +import { act, fireEvent, render, screen } from '@testing-library/react'; +import { StoreProvider } from 'easy-peasy'; +import { enableFetchMocks } from 'jest-fetch-mock' +enableFetchMocks() +jest.mock('next/router', () => require('next-router-mock')); +import fetch from 'jest-fetch-mock' +import { store } from '../../services/store.service'; +import { CREATE_TRACK, LIST_TRACKS, VEHICLE_LIST } from '../../services/queries'; +import { MockedProvider } from '@apollo/react-testing'; +import { MemoryRouterProvider } from 'next-router-mock/dist/MemoryRouterProvider'; +import BoxToast from '../../components/layout/boxToast'; +import AddTrack from '../../pages/dco/addTrack'; +import TracksMain from '../../pages/dco/tracksMain'; +import { gql } from '@apollo/client'; +import { BoxToastProps } from '../../types'; +describe('Adding new tracks', () => { + const mockPerformanceMark = jest.fn(); + window.performance.mark = mockPerformanceMark; + // jest.useFakeTimers(); + beforeEach(() => { + fetch.resetMocks(); + }); + const toastMsg= "Track has been created successfuly" + + // for getting vehicles api list + const inputVal = [{ + "content": [ + { + "vin": "BBTEST00000000340", + "owner": null, + "ecomDate": "2022-12-15T21:30:32.105Z", + "country": "FR", + "model": "A-Class", + "brand": "Mercedes-Benz", + "region": null, + "instantiatedAt": null, + "createdAt": "2022-12-15T21:30:46.443421", + "updatedAt": "2023-02-22T00:58:01.734361", + "status": "DRAFT", + "type": "Virtual Vehicle", + "fleets": [ + { + "id": "256", + "name": "fleet bb", + "type": "STATIC" + }, + { + "id": "31", + "name": "411", + "type": "DYNAMIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "services": [], + "devices": [ + { + "id": "84c32703-982a-4efd-920b-b80977073b33", + "type": "TCU", + "status": "ACTIVE", + "createdAt": "2022-12-15T21:30:46.443445", + "gatewayId": "", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-20T14:09:25.437637", + "dmProtocolVersion": "1.0.2" + }, + { + "id": "36e5322b-92ce-4fb9-ac25-56d2ed0c4b53", + "type": "IVI", + "status": "DRAFT", + "createdAt": "2022-12-20T14:09:12.829388", + "gatewayId": "", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-20T14:09:25.48871", + "dmProtocolVersion": "1.0.2" + } + ] + }, + { + "vin": "BBTEST00000000341", + "owner": null, + "ecomDate": "2022-12-16T12:06:09.865Z", + "country": "FR", + "model": "A-Class", + "brand": "Mercedes-Benz", + "region": null, + "instantiatedAt": null, + "createdAt": "2022-12-16T12:06:28.208036", + "updatedAt": "2023-02-22T00:58:01.733471", + "status": "READY", + "type": "Virtual Vehicle", + "fleets": [ + { + "id": "256", + "name": "fleet bb", + "type": "STATIC" + }, + { + "id": "31", + "name": "411", + "type": "DYNAMIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "services": [], + "devices": [ + { + "id": "18d4285b-31c5-4e35-8b1c-ed931b3baed3", + "type": "TCU", + "status": "ACTIVE", + "createdAt": "2022-12-16T12:06:28.209091", + "gatewayId": "", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-30T16:44:49.170935", + "dmProtocolVersion": "1.0.2" + } + ] + }, + { + "vin": "VINTESTTBB0003014", + "owner": null, + "ecomDate": null, + "country": "GR", + "model": "A1", + "brand": "Audi", + "region": null, + "instantiatedAt": null, + "createdAt": "2022-12-19T09:41:52.37476", + "updatedAt": "2023-02-22T00:58:01.734666", + "status": "DRAFT", + "type": "Test Bench", + "fleets": [ + { + "id": "368", + "name": "newbb", + "type": "STATIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "services": [], + "devices": [ + { + "id": "TESTDEVICE3014", + "type": "TCU", + "status": "READY", + "createdAt": "2022-12-19T09:41:52.374781", + "gatewayId": "", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-19T09:41:53.877784", + "dmProtocolVersion": "1.0.2" + } + ] + }, + { + "vin": "VINTESTTBB0003013", + "owner": null, + "ecomDate": null, + "country": "LU", + "model": "Puma", + "brand": "Ford", + "region": null, + "instantiatedAt": null, + "createdAt": "2022-12-19T09:41:56.025012", + "updatedAt": "2023-02-22T00:58:01.729348", + "status": "DRAFT", + "type": "Test Bench", + "fleets": [ + { + "id": "368", + "name": "newbb", + "type": "STATIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "services": [], + "devices": [ + { + "id": "TESTDEVICE3013", + "type": "TCU", + "status": "READY", + "createdAt": "2022-12-19T09:41:56.025058", + "gatewayId": "", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-19T09:41:56.025058", + "dmProtocolVersion": "1.0.2" + } + ] + }, + { + "vin": "VINTESTTBB0002940", + "owner": null, + "ecomDate": null, + "country": "PL", + "model": "Polo", + "brand": "VW", + "region": null, + "instantiatedAt": null, + "createdAt": "2022-12-19T09:42:03.857758", + "updatedAt": "2023-02-22T00:58:01.739486", + "status": "DRAFT", + "type": "Real Vehicle", + "fleets": [ + { + "id": "6197", + "name": "cgdszy", + "type": "STATIC" + }, + { + "id": "4303", + "name": "static", + "type": "STATIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "services": [], + "devices": [ + { + "id": "TESTDEVICE2940", + "type": "IVI", + "status": "DRAFT", + "createdAt": "2022-12-19T09:42:03.857775", + "gatewayId": "", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-19T09:42:03.857775", + "dmProtocolVersion": "1.0.2" + } + ] + }, + { + "vin": "VINTESTTBB0002939", + "owner": null, + "ecomDate": null, + "country": "GR", + "model": "Almera", + "brand": "Nissan", + "region": null, + "instantiatedAt": null, + "createdAt": "2022-12-19T09:42:11.52793", + "updatedAt": "2023-02-22T00:58:01.729547", + "status": "DRAFT", + "type": "Real Vehicle", + "fleets": [ + { + "id": "32", + "name": "DM4382-Test-Dynamic-fleet_5", + "type": "DYNAMIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "services": [], + "devices": [] + }, + { + "vin": "TESTVIN0000003337", + "owner": null, + "ecomDate": "2022-01-01T00:00:00.000Z", + "country": "PL", + "model": "Insignia", + "brand": "Opel", + "region": null, + "instantiatedAt": null, + "createdAt": "2022-12-19T10:59:57.841533", + "updatedAt": "2023-02-22T00:58:01.73495", + "status": "READY", + "type": "Virtual Vehicle", + "fleets": [ + { + "id": "368", + "name": "newbb", + "type": "STATIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "services": [], + "devices": [ + { + "id": "41e5242c-aa6a-4def-ae1a-961d615b746f", + "type": "TCU", + "status": "READY", + "createdAt": "2022-12-19T10:59:57.841549", + "gatewayId": "", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-19T10:59:57.841549", + "dmProtocolVersion": "3.0.1" + } + ] + }, + { + "vin": "TESTVIN0000003340", + "owner": null, + "ecomDate": "2022-01-01T00:00:00.000Z", + "country": "PL", + "model": "Insignia", + "brand": "Opel", + "region": "EU", + "instantiatedAt": null, + "createdAt": "2022-12-19T11:00:05.613406", + "updatedAt": "2023-02-22T00:58:01.73921", + "status": "DRAFT", + "type": "Real Vehicle", + "fleets": [ + { + "id": "368", + "name": "newbb", + "type": "STATIC" + }, + { + "id": "4053", + "name": "Test", + "type": "STATIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "services": [], + "devices": [ + { + "id": "TESTDEVICE3340", + "type": "TCU", + "status": "DRAFT", + "createdAt": "2022-12-19T11:00:05.613424", + "gatewayId": null, + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-19T11:00:12.213828", + "dmProtocolVersion": "3.0.3" + } + ] + }, + { + "vin": "TESTVIN0000003408", + "owner": null, + "ecomDate": "2021-04-01", + "country": "PL", + "model": "Life", + "brand": "eGO", + "region": "EU", + "instantiatedAt": null, + "createdAt": "2022-12-19T11:00:15.114168", + "updatedAt": "2023-02-22T00:58:01.738612", + "status": "DRAFT", + "type": "Test Bench", + "fleets": [ + { + "id": "368", + "name": "newbb", + "type": "STATIC" + }, + { + "id": "4053", + "name": "Test", + "type": "STATIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "services": [], + "devices": [] + }, + { + "vin": "VINPM128167865193", + "owner": null, + "ecomDate": "2022-07-01", + "country": "FR", + "model": "5 Series", + "brand": "BMW", + "region": null, + "instantiatedAt": null, + "createdAt": "2022-12-19T13:17:01.946904", + "updatedAt": "2023-02-23T00:53:00.110147", + "status": "READY", + "type": "Test Bench", + "fleets": [ + { + "id": "31", + "name": "411", + "type": "DYNAMIC" + }, + { + "id": "1867", + "name": "Fleet_DM5172", + "type": "DYNAMIC" + }, + { + "id": "2478", + "name": "Fleet_DM5172", + "type": "DYNAMIC" + }, + { + "id": "3459", + "name": "Fleet_DM5172", + "type": "DYNAMIC" + }, + { + "id": "770", + "name": "Fleet_DM5172", + "type": "DYNAMIC" + }, + { + "id": "1042", + "name": "Fleet_DM5172", + "type": "DYNAMIC" + }, + { + "id": "3829", + "name": "Fleet_DM5172", + "type": "DYNAMIC" + }, + { + "id": "6295", + "name": "Fleet_DM5172", + "type": "DYNAMIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + }, + { + "id": "2082", + "name": "Fleet_DM5172", + "type": "DYNAMIC" + }, + { + "id": "6905", + "name": "Updated name", + "type": "DYNAMIC" + }, + { + "id": "6911", + "name": "Fleet_DM4624", + "type": "DYNAMIC" + } + ], + "services": [], + "devices": [ + { + "id": "DEVICEID665457956", + "type": "TCU", + "status": "READY", + "createdAt": "2022-12-19T13:17:01.946954", + "gatewayId": null, + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-19T13:17:01.946954", + "dmProtocolVersion": "1.01" + }, + { + "id": "DEVICEID773051816", + "type": "IVI", + "status": "READY", + "createdAt": "2022-12-19T13:17:01.946972", + "gatewayId": "DEVICEID665457956", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-19T13:17:01.946972", + "dmProtocolVersion": "1.01" + } + ] + } + ], + "empty": false, + "first": true, + "last": true, + "page": 0, + "size": 10, + "pages": 1, + "elements": 10, + "total": 10 + }] + const mockListVehicles = [{ + request: { + query: VEHICLE_LIST, + variables: { "search": null, "query": null, "page": 1, "size": 10, "sort": null } + }, + result: { data: { vehicleReadByQuery: inputVal } }, + }] + + // on click of save button + const mockList = [{ + request: { + query: CREATE_TRACK, + variables: { id: '' } + }, + result: { data: { createTrack: '' } }, + }] + + // on click of back button + const inputValTracksMain = { + content: { + id: '', + name: '', + state: '', + trackType: '', + vehicles: { + vin: '', + country: '', + } + }, + empty: '', + first: '', + last: '', + page: '', + size: '', + pages: '', + elements: '', + total: '', + + } + const mockListTracksMain = [{ + request: { + query: gql(LIST_TRACKS), + variables: { trackPattern: '', page: '', size: '' } + }, + result: { data: { searchTrackByPattern: inputValTracksMain } }, + }] + + test('add track page render', async () => { + render( + // @ts-ignore + <StoreProvider store={store}> + <MemoryRouterProvider url="/dco/addTrack"> + <MockedProvider mocks={mockList} addTypename={false}> + <AddTrack /> + </MockedProvider> + </MemoryRouterProvider> + </StoreProvider> + ) + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 0)); + }); + render( + // @ts-ignore + <StoreProvider store={store}> + <MemoryRouterProvider url="/dco/addTrack"> + <MockedProvider mocks={mockListVehicles} addTypename={false}> + <AddTrack /> + </MockedProvider> + </MemoryRouterProvider> + </StoreProvider> + ) + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 0)); + }); + store.getActions().setInvert(true); + store.getActions().setPage(1); + }); + + test('on click of back button', async () => { + render( + // @ts-ignore + <StoreProvider store={store}> + <MemoryRouterProvider url="/dco/addTrack"> + <MockedProvider mocks={mockList} addTypename={false}> + <AddTrack /> + </MockedProvider> + </MemoryRouterProvider> + </StoreProvider> + ) + const checkBtn = screen.getByTestId("backButtondata"); + fireEvent.click(checkBtn); + render( + // @ts-ignore + <StoreProvider store={store}> + <MemoryRouterProvider url="/dco/tracksMain"> + <MockedProvider mocks={mockListTracksMain} addTypename={false}> + <TracksMain /> + </MockedProvider> + </MemoryRouterProvider> + </StoreProvider> + ) + store.getActions().setInvert(true); + }); + + test('on click of save button', () => { + render( + // @ts-ignore + <StoreProvider store={store}> + <MemoryRouterProvider url="/dco/addTrack"> + <MockedProvider mocks={mockList} addTypename={false}> + <AddTrack /> + </MockedProvider> + </MemoryRouterProvider> + </StoreProvider> + ) + const checkBtn = screen.getByTestId("saveButton"); + fireEvent.click(checkBtn); + render( + // @ts-ignore + <StoreProvider store={store}> + <MemoryRouterProvider url="/dco/tracksMain"> + <MockedProvider mocks={mockListTracksMain} addTypename={false}> + <TracksMain /> + </MockedProvider> + </MemoryRouterProvider> + </StoreProvider> + ) + render( + // @ts-ignore + <StoreProvider store={store}> + <BoxToast toastMsg={toastMsg}></BoxToast> + </StoreProvider> + ) + store.getActions().setInvert(true); + }) + +}) diff --git a/developer-console-ui/app/__tests__/dco.test.tsx b/developer-console-ui/app/__tests__/dco.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..07fe255a4d4148f5b583277f24357e45b57f7a0f --- /dev/null +++ b/developer-console-ui/app/__tests__/dco.test.tsx @@ -0,0 +1,24 @@ +import { enableFetchMocks } from 'jest-fetch-mock' +import { cleanup, fireEvent, render, screen } from '@testing-library/react'; +enableFetchMocks() +jest.mock('next/router', () => require('next-router-mock')); +import fetch from 'jest-fetch-mock' +import { store } from '../services/store.service'; +import Dco from '../pages/dco'; +import { StoreProvider } from 'easy-peasy'; +describe('dco', () => { + it("should render dco", () => { + render( + // @ts-ignore + <StoreProvider store={store}> + <Dco /> + </StoreProvider> + ); + store.getActions().setCount(0); + }); + beforeEach(() => { + fetch.resetMocks(); + }); + store.getActions().setPage(1); +}) + diff --git a/developer-console-ui/app/__tests__/error.test.tsx b/developer-console-ui/app/__tests__/error.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a614d55ba5281284a6be139f9d8258f1da65efcc --- /dev/null +++ b/developer-console-ui/app/__tests__/error.test.tsx @@ -0,0 +1,34 @@ +import { enableFetchMocks } from 'jest-fetch-mock' +import { fireEvent, render, screen } from '@testing-library/react'; +enableFetchMocks() +jest.mock('next/router', () => require('next-router-mock')); +import fetch from 'jest-fetch-mock' +import { store } from '../services/store.service'; +import { StoreProvider } from 'easy-peasy'; +import Error from '../pages/error'; +import { onSubmit } from '../services/credentials.service'; +describe('error page', () => { + it("should render error page and fire submit btn", async () => { + render( + // @ts-ignore + <StoreProvider store={store}> + <Error /> + </StoreProvider> + ); + const submit = screen.getByTestId("submitBtn"); + fireEvent.click(submit); + try { + return await (onSubmit('dco','dco',jest.fn(),'')) + .then((result) => result) + .then((res:any)=>{}) + } catch (e) { + return e; + } + store.getActions().setCount(0); + }); + beforeEach(() => { + fetch.resetMocks(); + }); + store.getActions().setPage(1); +}) + diff --git a/developer-console-ui/app/__tests__/index.test.tsx b/developer-console-ui/app/__tests__/index.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f0e31e4a1fcc46a384b5a316f52abe6386fad03c --- /dev/null +++ b/developer-console-ui/app/__tests__/index.test.tsx @@ -0,0 +1,45 @@ +import '@testing-library/jest-dom'; +import { render } from '@testing-library/react'; +import { StoreProvider } from 'easy-peasy'; +import { MemoryRouterProvider } from 'next-router-mock/dist/MemoryRouterProvider'; +import Home from '../pages'; +import { store } from '../services/store.service'; + +describe("Index page", () => { + it("should render index", () => { + render( + // @ts-ignore + <StoreProvider store={store}> + <Home /> + </StoreProvider> + ); + store.getActions().setInvert(true); + }); + it("should render count", () => { + render( + // @ts-ignore + <StoreProvider store={store}> + <Home /> + </StoreProvider> + ); + store.getActions().setCount(0); + }); + it("should render id", () => { + render( + // @ts-ignore + <StoreProvider store={store}> + <Home /> + </StoreProvider> + ); + }); + it("should render spinner", () => { + render( + // @ts-ignore + <StoreProvider store={store}> + <MemoryRouterProvider url="/dco"> + <Home /> + </MemoryRouterProvider> + </StoreProvider> + ); + }); +}); diff --git a/developer-console-ui/app/__tests__/login.test.tsx b/developer-console-ui/app/__tests__/login.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f70045d81f5e1eb2c74129b97661ebb02266fb6b --- /dev/null +++ b/developer-console-ui/app/__tests__/login.test.tsx @@ -0,0 +1,26 @@ +import { enableFetchMocks } from 'jest-fetch-mock' +import { fireEvent, render, screen } from '@testing-library/react'; +enableFetchMocks() +jest.mock('next/router', () => require('next-router-mock')); +import fetch from 'jest-fetch-mock' +import { store } from '../services/store.service'; +import { StoreProvider } from 'easy-peasy'; +import Login from '../pages/login'; +describe('login ', () => { + it("should render login page", () => { + render( + // @ts-ignore + <StoreProvider store={store}> + <Login /> + </StoreProvider> + ); + const submit = screen.getByTestId("submitBtn"); + fireEvent.click(submit); + store.getActions().setCount(0); + }); + beforeEach(() => { + fetch.resetMocks(); + }); + store.getActions().setPage(1); +}) + diff --git a/developer-console-ui/app/__tests__/logout.test.tsx b/developer-console-ui/app/__tests__/logout.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b81e3a56385b7f0a107c504ca76cd2519409f43c --- /dev/null +++ b/developer-console-ui/app/__tests__/logout.test.tsx @@ -0,0 +1,26 @@ +import { enableFetchMocks } from 'jest-fetch-mock' +import { fireEvent, render, screen } from '@testing-library/react'; +enableFetchMocks() +jest.mock('next/router', () => require('next-router-mock')); +import fetch from 'jest-fetch-mock' +import { store } from '../services/store.service'; +import { StoreProvider } from 'easy-peasy'; +import Logout from '../pages/logout'; +describe('logout', () => { + it("should render logout app", () => { + render( + // @ts-ignore + <StoreProvider store={store}> + <Logout /> + </StoreProvider> + ); + const login = screen.getByTestId("loginBtn"); + fireEvent.click(login); + store.getActions().setCount(0); + }); + beforeEach(() => { + fetch.resetMocks(); + }); + store.getActions().setPage(1); +}) + diff --git a/developer-console-ui/app/__tests__/scenario/scenario.test.tsx b/developer-console-ui/app/__tests__/scenario/scenario.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..17cf8171e37e191906ebe9f807135c974b54119a --- /dev/null +++ b/developer-console-ui/app/__tests__/scenario/scenario.test.tsx @@ -0,0 +1,38 @@ +import { act, render } from '@testing-library/react'; +import { StoreProvider } from 'easy-peasy'; +import { MockedProvider } from "@apollo/react-testing"; +import { store } from '../../services/store.service'; +import { MemoryRouterProvider } from 'next-router-mock/dist/MemoryRouterProvider'; +import { enableFetchMocks } from 'jest-fetch-mock' +enableFetchMocks() +jest.mock('next/router', () => require('next-router-mock')); +import fetch from 'jest-fetch-mock' +import Scenario from '../../pages/dco/scenario'; +describe('Table render in scenario', () => { + const useRouter = jest.spyOn(require('next/router'), 'useRouter'); + useRouter.mockImplementation(() => ({ + pathname: '/dco/scenario', + })); + test('table with props', async () => { + store.getActions().setCount(0); + useRouter + render( + // @ts-ignore + <StoreProvider store={store}> + <MemoryRouterProvider url="/dco/scenario"> + <MockedProvider> + <Scenario/> + </MockedProvider> + </MemoryRouterProvider> + </StoreProvider> + ) + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 0)); + }); + store.getActions().setInvert(true); + }); + beforeEach(() => { + fetch.resetMocks(); + }); + +}) diff --git a/developer-console-ui/app/__tests__/scenario/scenarioList.test.tsx b/developer-console-ui/app/__tests__/scenario/scenarioList.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e6de227cc8ac69872e6fc373bde92ab7e284a76b --- /dev/null +++ b/developer-console-ui/app/__tests__/scenario/scenarioList.test.tsx @@ -0,0 +1,47 @@ +import { act, render } from '@testing-library/react'; +import { StoreProvider } from 'easy-peasy'; +import { MockedProvider } from "@apollo/react-testing"; +import { store } from '../../services/store.service'; +import { MemoryRouterProvider } from 'next-router-mock/dist/MemoryRouterProvider'; +import { enableFetchMocks } from 'jest-fetch-mock' +enableFetchMocks() +jest.mock('next/router', () => require('next-router-mock')); +import fetch from 'jest-fetch-mock' +import ScenarioList from '../../pages/dco/scenario/scenarioList'; +import { GET_SCENARIO } from '../../services/queries'; +import { gql } from '@apollo/client'; +describe('Table render in scenario', () => { + const useRouter = jest.spyOn(require('next/router'), 'useRouter'); + useRouter.mockImplementation(() => ({ + pathname: '/dco/scenario', + })); + const mockList = [{ + request: { + query: gql(GET_SCENARIO), + variables: { scenarioPattern: 'sce', page: 0, size: 10 } + }, + result: { data: { searchScenarioByPattern: '' } }, + }] + test('table with props', async () => { + store.getActions().setCount(0); + useRouter + render( + // @ts-ignore + <StoreProvider store={store}> + <MemoryRouterProvider url="/dco/scenario"> + <MockedProvider mocks={mockList}> + <ScenarioList /> + </MockedProvider> + </MemoryRouterProvider> + </StoreProvider> + ) + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 0)); + }); + store.getActions().setInvert(true); + }); + beforeEach(() => { + fetch.resetMocks(); + }); + +}) diff --git a/developer-console-ui/app/__tests__/services/credentials.test.tsx b/developer-console-ui/app/__tests__/services/credentials.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..58a3a46e3f10be1f0e57f479d1a42d0c15b97c98 --- /dev/null +++ b/developer-console-ui/app/__tests__/services/credentials.test.tsx @@ -0,0 +1,21 @@ +import { store } from "../../services/store.service"; +import { enableFetchMocks } from 'jest-fetch-mock' +enableFetchMocks() +jest.mock('next/router', () => require('next-router-mock')); +import fetch from 'jest-fetch-mock' +import { onSubmit } from "../../services/credentials.service"; +describe('credentials', () => { + it('on submit', async () => { + try { + return await (onSubmit('dco','dco',jest.fn(),'')) + .then((result) => result) + .then((res:any)=>{}) + } catch (e) { + return e; + } + }) + beforeEach(() => { + fetch.resetMocks(); + }); + store.getActions().setPage(1); +}) \ No newline at end of file diff --git a/developer-console-ui/app/__tests__/services/functionScenario.test.tsx b/developer-console-ui/app/__tests__/services/functionScenario.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f2fac242b967e413ab999274ca72d913ffceae4b --- /dev/null +++ b/developer-console-ui/app/__tests__/services/functionScenario.test.tsx @@ -0,0 +1,230 @@ +import { enableFetchMocks } from 'jest-fetch-mock' +enableFetchMocks() +jest.mock('next/router', () => require('next-router-mock')); +import fetch from 'jest-fetch-mock' +import { callUploadAxiosAPIForNewScenario, callUploadAxiosAPIForUpdateScenario, getFileSIzeInService, getLibData, getUploadFormDataForNewScenario, getUploadFormDataForUpdateScenario, handleNewScenarioSubmitInService, handleUpdateScenarioSubmitInService, libRowData, onClickScenario, resetFormForScenario, scenarioDataFromMap, setToastMessageForDeleteScenario, setToastMessageForNewScenario, setToastMessageForUpdateScenario, uploadFileCondition } from '../../services/functionScenario.services'; +import { act } from 'react-dom/test-utils'; +const formData = new FormData() +const reset = { + setName: jest.fn(), + setType: jest.fn(), + setDescription: jest.fn(), + setFileSizeError: jest.fn(), + setFileNameError: jest.fn() +} +const data = { + data: { + data: { + searchScenarioByPattern: { + content: [{ + createdAt: "2023-01-31T11:45:54.409678Z", + createdBy: "abc@t-systems.com", + description: "", + files: [{ + checksum: "3E25960A79DBC69B674CD4EC67A72C62", + id: "ed78720b-0615-4c2b-9128-0c805790a1fc", + path: process.env.NEXT_PUBLIC_FILE, + size: "11", + updatedBy: "abc@t-systems.com", + updatedOn: "2023-02-06T09:03:49.714965Z" + }], + id: "fe7f4c94-c21a-4a4b-a3fc-c1b309b0f5f0", + lastModifiedBy: "abc@t-systems.com", + name: "Scenario 7", + status: "CREATED", + type: "CAN" + }], + elements: 15, + empty: false, + first: true, + last: false, + page: 0, + pages: 2, + size: 15, + total: 16 + } + } + } +} +const setFunctions = { + setComponentValue: jest.fn(), + setVersionList: jest.fn(), + setTypeList: jest.fn(), + setDeviceList: jest.fn(), + setTypeValue: jest.fn(), + setVersionValue: jest.fn(), + setBuildValue: jest.fn(), + setDeviceValue: jest.fn(), + setFileSizeError: jest.fn(), + setFileNameError: jest.fn(), + setUploadFile: jest.fn(), + setToastMsg: jest.fn(), + setSuccessMsgScenario: jest.fn(), + setToastOpen: jest.fn(), + setToastOpenScenario: jest.fn(), + setArr: jest.fn(), + setName: jest.fn(), + setType: jest.fn(), + setDescription: jest.fn(), + setNameError: jest.fn(), + setTypeError: jest.fn(), + setFileError: jest.fn() +}; +describe('scenario test cases', () => { + const mockPerformanceMark = jest.fn(); + window.performance.mark = mockPerformanceMark; + jest.useFakeTimers(); + beforeEach(() => { + fetch.resetMocks(); + }); + it('scenarioDataFromMap', async () => { + let result = { + data: { + searchScenarioByPattern: { + content: [{ + "id": "96245c3a-d277-4ab3-ab38-cabfbf6eb2de", + "name": "scenario 6", + "description": "undefined", + "type": "MQTT", + "status": "CREATED", + "createdBy": "abc@t-systems.com", + "createdAt": "2023-04-07T07:35:05.455797Z", + "lastModifiedBy": "abc@t-systems.com", + "lastModifiedAt": "", + "file": { + "id": "d3f25c44-0f5c-4ff7-87c1-ad5882829834", + "path": "http://localhost:9000/scenario-library-service/scenario/96245c3a-d277-4ab3-ab38-cabfbf6eb2de/files/scenarioLib.txt", + "size": "8", + "checksum": "6D72EBEDD73D23B27BF7AEC86F55F9BB", + "updatedBy": "abc@t-systems.com", + "updatedOn": "2023-04-07T07:35:05.561311Z" + } + }] + } + } + } + expect(scenarioDataFromMap(result)).toStrictEqual([{ "createdBy": "abc@t-systems.com", "delete": "", "description": "undefined", "filename": undefined, "lastUpdated": "Invalid Date, Invalid Date", "scenario": "scenario 6", "sid": "96245c3a-d277-4ab3-ab38-cabfbf6eb2de", "type": "MQTT" }]); + }) + it('get scenario lib row data', async () => { + let result = { + data: { + searchScenarioByPattern: { + content: [{ + createdAt: "2023-03-29T11:05:08.201106Z", + createdBy: "abc@t-systems.com", + description: "undefined", + file: { + checksum: "6D72EBEDD73D23B27BF7AEC86F55F9BB", + id: "fc236ea9-30a4-4acb-9f73-720f38733364", + path: "http://localhost:9000/scenario-library-service/scenario/bcbb76e2-650f-4ce0-a7da-b3082a58b8f3/files/scenarioLib.txt", + size: "8", + updatedBy: "abc@t-systems.com", + updatedOn: "2023-03-29T11:05:08.542815Z" + }, + id: "bcbb76e2-650f-4ce0-a7da-b3082a58b8f3", + lastModifiedAt: "", + lastModifiedBy: "abc@t-systems.com", + name: "scenario 1", + status: "CREATED", + type: "CAN" + }], + elements: 10, + empty: false, + first: true, + last: true, + page: 0, + pages: 1, + size: 10, + total: 1 + } + } + } + expect(libRowData(result)).toStrictEqual([{ "check": "", "createdBy": "abc@t-systems.com", "description": "undefined", "filename": "scenarioLib.txt", "lastUpdated": "Invalid Date, Invalid Date", "menu": "", "scenario": "scenario 1", "sid": "bcbb76e2-650f-4ce0-a7da-b3082a58b8f3", "type": "CAN" }]) + } + ) + it('onClickScenario', async () => { + expect(onClickScenario('', jest.fn(), jest.fn())).toBe(undefined); + }) + it('handleNewScenarioSubmitInService', async () => { + expect(handleNewScenarioSubmitInService({ text: '', name: '', type: '', selectedUploadFile: '' }, 'abc@t-systems.com', { setName: jest.fn(), setType: jest.fn(), setDescription: jest.fn(), setUploadFile: jest.fn(), setFileSizeError: jest.fn(),setFileNameError: jest.fn(), setToastMsg: jest.fn(), setNameError: jest.fn(), setTypeError: jest.fn(), setFileError: jest.fn() }, jest.fn(), jest.fn())).toBe(undefined); + expect(handleNewScenarioSubmitInService({ text: 'Scenario test', name: 'Scenario', type: 'MQTT', selectedUploadFile: 'sce.txt' }, 'abc@t-systems.com', { setName: jest.fn(), setType: jest.fn(), setDescription: jest.fn(), setUploadFile: jest.fn(), setFileSizeError: jest.fn(), setToastMsg: jest.fn(), setNameError: jest.fn(), setFileNameError: jest.fn(),setTypeError: jest.fn(), setFileError: jest.fn() }, jest.fn(), jest.fn())).toBe(undefined); + }) + it('handleUpdateScenarioSubmitInService', async () => { + expect(handleUpdateScenarioSubmitInService({ text: '', name: '', type: '', selectedUploadFile: '' }, 'abc@t-systems.com', { setName: jest.fn(), setType: jest.fn(), setDescription: jest.fn(), setUploadFile: jest.fn(), setFileSizeError: jest.fn(), setFileNameError: jest.fn(),setSuccessMsgScenario: jest.fn(), setNameError: jest.fn(), setTypeError: jest.fn(), setFileError: jest.fn() }, jest.fn(), jest.fn(), { asPath: '/dco/scenario', replace: jest.fn() })).toBe(undefined); + expect(handleUpdateScenarioSubmitInService({ text: 'Scenario test', name: 'Scenario', type: 'MQTT', selectedUploadFile: 'sce.txt' }, 'abc@t-systems.com', { setName: jest.fn(), setType: jest.fn(), setDescription: jest.fn(), setUploadFile: jest.fn(), setFileSizeError: jest.fn(),setFileNameError: jest.fn(), setSuccessMsgScenario: jest.fn(), setNameError: jest.fn(), setTypeError: jest.fn(), setFileError: jest.fn() }, jest.fn(), jest.fn(), { asPath: '/dco/scenario', replace: jest.fn() })).toBe(undefined); + }) + it('getFileSIzeInService', async () => { + expect(getFileSIzeInService({ target: { value: '.txt', files: [{ size: 10,name:'abc_abc.txt' }] } }, jest.fn(), jest.fn(), 0, 10000, jest.fn(),jest.fn())).toBe(undefined); + expect(getFileSIzeInService({ target: { value: '.odx', files: [{ size: 10,name:'abc_abc.txt' }] } }, jest.fn(), jest.fn(), 0, 10000, jest.fn(),jest.fn())).toBe(undefined); + expect(getFileSIzeInService({ target: { value: '', files: [{name:''}] } }, jest.fn(), jest.fn(), 0, 10000, jest.fn(),jest.fn())).toBe(undefined); + }) + it('uploadFileCondition', async () => { + expect(uploadFileCondition(1, 10, 10000, 0, jest.fn(), { target: { value: '.txt', files: [{ size: 10 }] } }, + jest.fn())).toBe(false); + expect(uploadFileCondition(0, 0, 10000, 0, jest.fn(), { target: { value: '.txt', files: [{ size: 10 }] } }, + jest.fn())).toBe(true); + }) + it('tests setToastMessageForDeleteScenario', async () => { + expect(setToastMessageForDeleteScenario({ deleteScenario: "" }, jest.fn(), jest.fn(), "success")).toBe(true) + expect(setToastMessageForDeleteScenario({ deleteScenario: "" }, jest.fn(), jest.fn(), "")).toBe(false) + const callback = jest.fn(); + setToastMessageForDeleteScenario({ deleteScenario: "" }, callback, callback, "") + jest.runAllTimers(); + expect(callback).toBeCalled(); + expect(callback).toHaveBeenCalledTimes(3); + }) + test('setToastMessageForNewScenario', async () => { + expect( + setToastMessageForNewScenario({ data: { createScenario: 'test' } }, setFunctions.setToastMsg, jest.fn(), setFunctions.setToastOpen, setFunctions)).toBe(false); + expect( + setToastMessageForNewScenario({ errors: [{ message: 'test' }] }, setFunctions.setToastMsg, jest.fn(), setFunctions.setToastOpen, setFunctions)).toBe(true); + jest.runAllTimers(); + }) + test('setToastMessageForUpdateScenario', async () => { + expect( + setToastMessageForUpdateScenario({ data: { updateScenario: 'test' } }, setFunctions.setSuccessMsgScenario, jest.fn(), setFunctions.setToastOpenScenario)).toBe(false); + jest.runAllTimers(); + }) + it('tests callUploadAxiosAPIForUpdateScenario', async () => { + try { + return await callUploadAxiosAPIForUpdateScenario({ selectedUploadFile: 'TEST' }, '') + } catch (e) { + return []; + } + }) + it('tests getUploadFormDataForNewScenario', () => { + let result = getUploadFormDataForNewScenario('', '', '', '', ''); + expect(formData).toEqual(new FormData()); + }) + + it('tests resetFormForScenario', () => { + expect(resetFormForScenario(reset)).toBe(undefined); + }) + it('tests getUploadFormDataForUpdateScenario', async () => { + try { + return await (getUploadFormDataForUpdateScenario( + '123455', '', 'test.txt', '.txt', + 'test file', 'abc@t-systems.com')) + } catch (e) { + return e; + } + }) + it('tests getLibData', async () => { + try { + return await (getLibData(1, 'sc')).then((res) => res.json()) + .then((result) => result) + } catch (e) { + return e; + } + }) + it('tests callUploadAxiosAPIForNewScenario', async () => { + try { + return await callUploadAxiosAPIForNewScenario({ + selectedUploadFile: 'TEST', name: 'file.txt', + type: '.txt', description: '' + }, 'abc@t-systems.com') + } catch (e) { + return { data: { errors: [{ message: '' }] } }; + } + }) +}) \ No newline at end of file diff --git a/developer-console-ui/app/__tests__/services/functionShared.test.tsx b/developer-console-ui/app/__tests__/services/functionShared.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..6a799540989ac11fe77ef5f94c606f3459e25f9b --- /dev/null +++ b/developer-console-ui/app/__tests__/services/functionShared.test.tsx @@ -0,0 +1,55 @@ +import { avoidSplChars, checkRoute, displayBrandVal, getValArray, onLoadMore } from "../../services/functionShared"; +import { store } from "../../services/store.service"; + +describe('shared test cases', () => { + it('onLoadMore', async () => { + expect(onLoadMore(jest.fn(), jest.fn(), jest.fn(), 1, 1)).toBe(undefined); + }) + it('getValArray', async () => { + expect(getValArray([""], "")).toEqual([]); + }), + it('tests displayBrandVal', () => { + expect(displayBrandVal("Audi")).toBe('logo-audi'); + expect(displayBrandVal("Mercedes")).toBe('mercedes'); + expect(displayBrandVal("BMW")).toBe('logo-bmw'); + expect(displayBrandVal("eGO")).toBe('logo-ego'); + expect(displayBrandVal("Ford")).toBe('logo-ford'); + expect(displayBrandVal("Mitsubishi")).toBe('logo-mitsubishi'); + expect(displayBrandVal("Nissan")).toBe('logo-nissan'); + expect(displayBrandVal("Porsche")).toBe('logo-porsche'); + expect(displayBrandVal("Renault")).toBe('logo-renault'); + expect(displayBrandVal("Skoda")).toBe('logo-skoda'); + expect(displayBrandVal("Volvo")).toBe('logo-volvo'); + expect(displayBrandVal("VW")).toBe('logo-vw'); + expect(displayBrandVal("Volkswagen")).toBe('logo-vw'); + expect(displayBrandVal("test")).toBe('vehicle'); + }) + it('not allowing special characters', () => { + expect(avoidSplChars(jest.fn())).toBe(undefined); + }) + it('check route path', () => { + const useRouter = jest.spyOn(require('next/router'), 'useRouter'); + useRouter.mockImplementation(() => ({ + pathname: '/dco', + + })); + const useRouter2 = jest.spyOn(require('next/router'), 'useRouter'); + useRouter.mockImplementation(() => ({ + pathname: '/dco/tracksMain', + + })); + const useRouter3 = jest.spyOn(require('next/router'), + 'useRouter'); + useRouter.mockImplementation(() => ({ + pathname: '/dco/simulation', + + })); + expect(checkRoute('/dco', useRouter, '/dco/scenario')).toBe("/dco"); + expect(checkRoute('/dco/tracksMain', useRouter2, '/dco/tracksMain') + ).toBe("/dco/tracksMain"); + expect(checkRoute('/dco/simulation', useRouter3, '/dco/simulation') + ).toBe("/dco/simulation"); + + }) + store.getActions().setPage(1); +}) \ No newline at end of file diff --git a/developer-console-ui/app/__tests__/services/functionSimulation.test.tsx b/developer-console-ui/app/__tests__/services/functionSimulation.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c060a64bf229e2869867f78c4bdf5ca6b2ccafdf --- /dev/null +++ b/developer-console-ui/app/__tests__/services/functionSimulation.test.tsx @@ -0,0 +1,94 @@ +import { enableFetchMocks } from 'jest-fetch-mock' +enableFetchMocks() +jest.mock('next/router', () => require('next-router-mock')); +import fetch from 'jest-fetch-mock' +import { clearAll, getSimData, launchSimulation, onClickNewSimulation, onLaunchedSimulation, simRowData } from '../../services/functionSimulation.service'; +import { store } from '../../services/store.service'; +import { act } from 'react-dom/test-utils'; +describe('scenario test cases', () => { + const mockPerformanceMark = jest.fn(); + const variable = {title:'Scenario', scenario: [{checked:true,id:'344'},{checked:false,id:'12'}], track: [{checked:true,id:'233'},{checked:false,id:'239'}],scenarioType:'MQTT' } as const + const setVariable = { setTitleError: jest.fn(), setSTypeError: jest.fn(), setTrackError: jest.fn(), setScenarioError: jest.fn() } + + const variable2 = {title:null, scenario: [], track: [],scenarioType:null } as const + const setVariable2 = { setTitleError: jest.fn(), setSTypeError: jest.fn(), setTrackError: jest.fn(), setScenarioError: jest.fn() } + + const setVariables={setTitle:jest.fn(), setDescription:jest.fn(), setEnvironment:jest.fn(), setPlatform:jest.fn(), setSelectedscenario:jest.fn(), setSelectedtrack:jest.fn(), setScenarioType:jest.fn(), setHardware:jest.fn(), setSearchval:jest.fn(), setTitleError:jest.fn(), setSTypeError:jest.fn(),setTrackError:jest.fn(), setScenarioError:jest.fn(),} + window.performance.mark = mockPerformanceMark; + jest.useFakeTimers(); + beforeEach(() => { + fetch.resetMocks(); + }); + it('get simulation row data', () => { + let result = simRowData( + { + simulationReadByQuery: { + content: [{ + brands: [], + createdBy: "abc@t-systems.com", + description: "", + environment: "", + hardware: "", + id: "e0f4430b-5b9c-4cb8-9e39-bea5f31ece9a", + name: "sim", + noOfScenarios: 1, + noOfVehicle: 0, + platform: "", + scenarioType: "Over-The-Air Service", + startDate: "2023-03-29T11:06:19.695468Z", + status: "Timeout" + }], + elements: 10, + empty: false, + first: true, + last: true, + page: 0, + pages: 1, + size: 10, + total: 1 + } + } + ); + expect(result).toBe(undefined); + let result2 = simRowData( + { + simulationReadByQuery: { + content: [], + elements: 0, + empty: false, + first: true, + last: true, + page: 0, + pages: 1, + size: 1, + total: 1 + } + } + ); + expect(result2).toBe(undefined); + }) + it('launch simulation', () => { + expect(launchSimulation(variable, jest.fn(), setVariable)).toBe(undefined); + expect(launchSimulation(variable2, jest.fn(), setVariable2)).toBe(undefined); + }) + + it('launched simulation', () => { + expect(onLaunchedSimulation(jest.fn(), jest.fn(), jest.fn(), jest.fn(), [], true)).toBe(undefined); + jest.runAllTimers(); + + expect(onLaunchedSimulation(jest.fn(), jest.fn(), jest.fn(), jest.fn(), [], false)).toBe(undefined); + jest.runAllTimers(); + + expect.assertions(2); + }) + it('clearAll', () => { + expect(clearAll(setVariables)).toBe(undefined); + }) + it('clearAll', () => { + expect(onClickNewSimulation()).toBe(undefined); + jest.runAllTimers(); + }) + store.getActions().setSearchval('scen') + store.getActions().setSelectedscenario('234'); + store.getActions().setSelectedtrack('234'); +}) \ No newline at end of file diff --git a/developer-console-ui/app/__tests__/services/functionTrack.test.tsx b/developer-console-ui/app/__tests__/services/functionTrack.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..fbd4bce4d2a1e33f17a04a9bc35597eea9f75665 --- /dev/null +++ b/developer-console-ui/app/__tests__/services/functionTrack.test.tsx @@ -0,0 +1,637 @@ +import { enableFetchMocks } from 'jest-fetch-mock' +enableFetchMocks() +import { getToolTip, onClickDeleteTrack, setToastMessageForDeleteTrack, uploadFile, onselectionchange, saveNewTrack, onClickTrackColumn, mapDataToTable, deleteTrackFun, getVehicleList, getCompNVersion, getDevices, onSelectionChanged, selectedCheckboxFunction, getUploadFormDataForNewComponent, onClickMenuItem, onCompletedCreateTrack, trackRowData, getTrackData, onClickNewTrack, } from "../../services/functionTrack.service"; +jest.mock('next/router', () => require('next-router-mock')); +import fetch from 'jest-fetch-mock' +import { store } from '../../services/store.service'; +describe('test category', () => { + const mockPerformanceMark = jest.fn(); + const vehicleList = { findTrackById: { vehicles: [{ vin: "test", updatedAt: "", country: "", status: "", brand: "", devices: [{ type: "", id: "", components: [{ name: "", version: "" }] }] }] } } + window.performance.mark = mockPerformanceMark; + const setFunctions = { + setIsToastOpen: jest.fn(), + setToastMsg: jest.fn(), + setTitleError: jest.fn(), + setDurationError: jest.fn(), + setVehicleError: jest.fn(), + setButtonDisable: jest.fn(), + }; + // it('tests logout functionality', async () => { + // expect(logoutFunction({ data: 'logout' }, jest.fn())).toBe(undefined); + // }) + it('tests uploadFile', () => { + let result = uploadFile([{ size: 645692 }, { size: 645692 }]); + + expect(result).toBe(1.23); + }) + it('get trackRowData', () => { + let result = trackRowData({ + data: { + searchTrackByPattern: { + content: [{ + id: "d0b56403-9b45-4e32-b62d-6a4f7e409240", + name: "track", + state: "CREATED", + trackType: "Test", + vehicles: [{ + country: "FR", + vin: "BBTEST00000000341" + }, { + country: "LU", + vin: "VINTESTTBB0003013" + }] + }], + elements: 10, + empty: false, + first: true, + last: true, + page: 0, + pages: 1, + size: 10, + total: 1 + } + } + }); + expect(result).toStrictEqual([{ "check": "", "country": ["FR", "LU"], "delete": "", "numberofvehicles": 2, "trackID": "d0b56403-9b45-4e32-b62d-6a4f7e409240", "trackName": "track", "trackNameSim": "track", "trackStatus": "CREATED", "trackType": "Test" }]); + }) + it('get track data', () => { + // const data = await getTrackData(0,'track'); + // expect(data).toBe('peanut butter'); + // const data = { "searchTrackByPattern": { "content": [{ "id": "ab2ce5db-a0ad-4b6b-a16f-7ba5760c9f06", "name": "track", "state": "CREATED", "trackType": "Test", "vehicles": [{ "vin": "BBTEST00000000340", "country": "FR" }] }], "empty": false, "first": true, "last": true, "page": 0, "size": 10, "pages": 1, "elements": 10, "total": 1 } } + // expect(getTrackData(0, 'track')).toBe(data) + getTrackData(0, 'track').catch(e => expect(e).toMatch('error')); + }) + const formData = new FormData() + it('tests onClickDeleteTrack', async () => { + expect(onClickDeleteTrack('', jest.fn(), jest.fn())).toBe(undefined) + }) + jest.useFakeTimers(); + it('tests setToastMessageForDeleteTrack', async () => { + expect(setToastMessageForDeleteTrack({ deleteTrack: "" }, jest.fn(), jest.fn(), "success")).toBe(true) + expect(setToastMessageForDeleteTrack({ deleteTrack: "" }, jest.fn(), jest.fn(), "")).toBe(false) + const callback = jest.fn(); + setToastMessageForDeleteTrack({ deleteTrack: "" }, callback, callback, "") + jest.runAllTimers(); + expect(callback).toBeCalled(); + expect(callback).toHaveBeenCalledTimes(4); + }) + it('tests getToolTip', async () => { + expect(getToolTip([{ country: "IND" }, { country: "GER" }])).toStrictEqual("IND, GER") + expect(getToolTip([{ country: "IND" }])).toStrictEqual("IND") + expect(getToolTip(["IND", "IND"])).toStrictEqual("IND, IND") + }) + it('tests onselectionchange', async () => { + expect(onselectionchange([{ data: { vin: "", country: "" } }], jest.fn(), [], jest.fn(), '', jest.fn())).toBe(undefined) + }) + it('tests saveNewTrack', async () => { + expect(saveNewTrack('Track3', [''], '0', 'description', jest.fn(), setFunctions)).toBe(undefined) + expect(saveNewTrack('title', [''], '6', 'test', jest.fn(), setFunctions)).toBe(undefined) + expect(saveNewTrack('Track 1', [''], '33', 'description', jest.fn(), setFunctions)).toBe(undefined) + expect(saveNewTrack('', [''], '', '', jest.fn(), setFunctions)).toBe(undefined) + }) + it('onClickTrackColumn', async () => { + expect(onClickTrackColumn('', jest.fn(), jest.fn())).toBe(undefined) + jest.runAllTimers(); + expect(window.performance.mark).toBe(undefined); + const useRouter = jest.spyOn(require('next/router'), 'useRouter'); + useRouter.mockImplementation(() => ({ + pathname: '/', + })); + }) + it('mapDataToTable', async () => { + let result={ "vehicleReadByQuery": { + "content": [ + { + "vin": "BBTEST00000000340", + "owner": null, + "ecomDate": "2022-12-15T21:30:32.105Z", + "country": "FR", + "model": "A-Class", + "brand": "Mercedes-Benz", + "region": null, + "instantiatedAt": null, + "createdAt": "2022-12-15T21:30:46.443421", + "updatedAt": "2023-02-22T00:58:01.734361", + "status": "DRAFT", + "type": "Virtual Vehicle", + "fleets": [ + { + "id": "256", + "name": "fleet bb", + "type": "STATIC" + }, + { + "id": "31", + "name": "411", + "type": "DYNAMIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "services": [], + "devices": [ + { + "id": "84c32703-982a-4efd-920b-b80977073b33", + "type": "TCU", + "status": "ACTIVE", + "createdAt": "2022-12-15T21:30:46.443445", + "gatewayId": "", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-20T14:09:25.437637", + "dmProtocolVersion": "1.0.2" + }, + { + "id": "36e5322b-92ce-4fb9-ac25-56d2ed0c4b53", + "type": "IVI", + "status": "DRAFT", + "createdAt": "2022-12-20T14:09:12.829388", + "gatewayId": "", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-20T14:09:25.48871", + "dmProtocolVersion": "1.0.2" + } + ] + }, + { + "vin": "BBTEST00000000341", + "owner": null, + "ecomDate": "2022-12-16T12:06:09.865Z", + "country": "FR", + "model": "A-Class", + "brand": "Mercedes-Benz", + "region": null, + "instantiatedAt": null, + "createdAt": "2022-12-16T12:06:28.208036", + "updatedAt": "2023-02-22T00:58:01.733471", + "status": "READY", + "type": "Virtual Vehicle", + "fleets": [ + { + "id": "256", + "name": "fleet bb", + "type": "STATIC" + }, + { + "id": "31", + "name": "411", + "type": "DYNAMIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "services": [], + "devices": [ + { + "id": "18d4285b-31c5-4e35-8b1c-ed931b3baed3", + "type": "TCU", + "status": "ACTIVE", + "createdAt": "2022-12-16T12:06:28.209091", + "gatewayId": "", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-30T16:44:49.170935", + "dmProtocolVersion": "1.0.2" + } + ] + }, + { + "vin": "VINTESTTBB0003014", + "owner": null, + "ecomDate": null, + "country": "GR", + "model": "A1", + "brand": "Audi", + "region": null, + "instantiatedAt": null, + "createdAt": "2022-12-19T09:41:52.37476", + "updatedAt": "2023-02-22T00:58:01.734666", + "status": "DRAFT", + "type": "Test Bench", + "fleets": [ + { + "id": "368", + "name": "newbb", + "type": "STATIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "services": [], + "devices": [ + { + "id": "TESTDEVICE3014", + "type": "TCU", + "status": "READY", + "createdAt": "2022-12-19T09:41:52.374781", + "gatewayId": "", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-19T09:41:53.877784", + "dmProtocolVersion": "1.0.2" + } + ] + }, + { + "vin": "VINTESTTBB0003013", + "owner": null, + "ecomDate": null, + "country": "LU", + "model": "Puma", + "brand": "Ford", + "region": null, + "instantiatedAt": null, + "createdAt": "2022-12-19T09:41:56.025012", + "updatedAt": "2023-02-22T00:58:01.729348", + "status": "DRAFT", + "type": "Test Bench", + "fleets": [ + { + "id": "368", + "name": "newbb", + "type": "STATIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "services": [], + "devices": [ + { + "id": "TESTDEVICE3013", + "type": "TCU", + "status": "READY", + "createdAt": "2022-12-19T09:41:56.025058", + "gatewayId": "", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-19T09:41:56.025058", + "dmProtocolVersion": "1.0.2" + } + ] + }, + { + "vin": "VINTESTTBB0002940", + "owner": null, + "ecomDate": null, + "country": "PL", + "model": "Polo", + "brand": "VW", + "region": null, + "instantiatedAt": null, + "createdAt": "2022-12-19T09:42:03.857758", + "updatedAt": "2023-02-22T00:58:01.739486", + "status": "DRAFT", + "type": "Real Vehicle", + "fleets": [ + { + "id": "6197", + "name": "cgdszy", + "type": "STATIC" + }, + { + "id": "4303", + "name": "static", + "type": "STATIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "services": [], + "devices": [ + { + "id": "TESTDEVICE2940", + "type": "IVI", + "status": "DRAFT", + "createdAt": "2022-12-19T09:42:03.857775", + "gatewayId": "", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-19T09:42:03.857775", + "dmProtocolVersion": "1.0.2" + } + ] + }, + { + "vin": "VINTESTTBB0002939", + "owner": null, + "ecomDate": null, + "country": "GR", + "model": "Almera", + "brand": "Nissan", + "region": null, + "instantiatedAt": null, + "createdAt": "2022-12-19T09:42:11.52793", + "updatedAt": "2023-02-22T00:58:01.729547", + "status": "DRAFT", + "type": "Real Vehicle", + "fleets": [ + { + "id": "32", + "name": "DM4382-Test-Dynamic-fleet_5", + "type": "DYNAMIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "services": [], + "devices": [] + }, + { + "vin": "TESTVIN0000003337", + "owner": null, + "ecomDate": "2022-01-01T00:00:00.000Z", + "country": "PL", + "model": "Insignia", + "brand": "Opel", + "region": null, + "instantiatedAt": null, + "createdAt": "2022-12-19T10:59:57.841533", + "updatedAt": "2023-02-22T00:58:01.73495", + "status": "READY", + "type": "Virtual Vehicle", + "fleets": [ + { + "id": "368", + "name": "newbb", + "type": "STATIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "services": [], + "devices": [ + { + "id": "41e5242c-aa6a-4def-ae1a-961d615b746f", + "type": "TCU", + "status": "READY", + "createdAt": "2022-12-19T10:59:57.841549", + "gatewayId": "", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-19T10:59:57.841549", + "dmProtocolVersion": "3.0.1" + } + ] + }, + { + "vin": "TESTVIN0000003340", + "owner": null, + "ecomDate": "2022-01-01T00:00:00.000Z", + "country": "PL", + "model": "Insignia", + "brand": "Opel", + "region": "EU", + "instantiatedAt": null, + "createdAt": "2022-12-19T11:00:05.613406", + "updatedAt": "2023-02-22T00:58:01.73921", + "status": "DRAFT", + "type": "Real Vehicle", + "fleets": [ + { + "id": "368", + "name": "newbb", + "type": "STATIC" + }, + { + "id": "4053", + "name": "Test", + "type": "STATIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "services": [], + "devices": [ + { + "id": "TESTDEVICE3340", + "type": "TCU", + "status": "DRAFT", + "createdAt": "2022-12-19T11:00:05.613424", + "gatewayId": null, + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-19T11:00:12.213828", + "dmProtocolVersion": "3.0.3" + } + ] + }, + { + "vin": "TESTVIN0000003408", + "owner": null, + "ecomDate": "2021-04-01", + "country": "PL", + "model": "Life", + "brand": "eGO", + "region": "EU", + "instantiatedAt": null, + "createdAt": "2022-12-19T11:00:15.114168", + "updatedAt": "2023-02-22T00:58:01.738612", + "status": "DRAFT", + "type": "Test Bench", + "fleets": [ + { + "id": "368", + "name": "newbb", + "type": "STATIC" + }, + { + "id": "4053", + "name": "Test", + "type": "STATIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "services": [], + "devices": [] + }, + { + "vin": "VINPM128167865193", + "owner": null, + "ecomDate": "2022-07-01", + "country": "FR", + "model": "5 Series", + "brand": "BMW", + "region": null, + "instantiatedAt": null, + "createdAt": "2022-12-19T13:17:01.946904", + "updatedAt": "2023-02-23T00:53:00.110147", + "status": "READY", + "type": "Test Bench", + "fleets": [ + { + "id": "31", + "name": "411", + "type": "DYNAMIC" + }, + { + "id": "1867", + "name": "Fleet_DM5172", + "type": "DYNAMIC" + }, + { + "id": "2478", + "name": "Fleet_DM5172", + "type": "DYNAMIC" + }, + { + "id": "3459", + "name": "Fleet_DM5172", + "type": "DYNAMIC" + }, + { + "id": "770", + "name": "Fleet_DM5172", + "type": "DYNAMIC" + }, + { + "id": "1042", + "name": "Fleet_DM5172", + "type": "DYNAMIC" + }, + { + "id": "3829", + "name": "Fleet_DM5172", + "type": "DYNAMIC" + }, + { + "id": "6295", + "name": "Fleet_DM5172", + "type": "DYNAMIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + }, + { + "id": "2082", + "name": "Fleet_DM5172", + "type": "DYNAMIC" + }, + { + "id": "6905", + "name": "Updated name", + "type": "DYNAMIC" + }, + { + "id": "6911", + "name": "Fleet_DM4624", + "type": "DYNAMIC" + } + ], + "services": [], + "devices": [ + { + "id": "DEVICEID665457956", + "type": "TCU", + "status": "READY", + "createdAt": "2022-12-19T13:17:01.946954", + "gatewayId": null, + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-19T13:17:01.946954", + "dmProtocolVersion": "1.01" + }, + { + "id": "DEVICEID773051816", + "type": "IVI", + "status": "READY", + "createdAt": "2022-12-19T13:17:01.946972", + "gatewayId": "DEVICEID665457956", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-19T13:17:01.946972", + "dmProtocolVersion": "1.01" + } + ] + } + ], + "empty": false, + "first": true, + "last": true, + "page": 0, + "size": 10, + "pages": 1, + "elements": 10, + "total": 10 + }} + let result2={vehicleReadByQuery:{content:[]}} + expect(mapDataToTable(result, jest.fn(),[] )).toBe(undefined); + // expect(mapDataToTable(result, jest.fn(),result )).toBe(undefined); + expect(mapDataToTable(result2, jest.fn(), [])).toBe(undefined); + // expect(mapDataToTable(result2, jest.fn(), result2)).toBe(undefined); + }) + it('deleteTrackFun', async () => { + expect(deleteTrackFun(jest.fn(), jest.fn(), true, { id: 1 })).toBe(undefined); + }) + it('getVehicleList', async () => { + expect(getVehicleList(vehicleList )).toStrictEqual([{"brand": undefined, "country": "", "devices": [{"components": [{"name": "", "version": ""}], "id": "", "type": ""}], "lastconnected": "", "status": "", "type": undefined, "vin": "test"}]); + }) + it('getCompNVersion', async () => { + // expect(getCompNVersion([[{ id: "", components: "" }]], [], [])).toEqual([""]); + // expect(getCompNVersion(undefined, [], [])).toBe(undefined); + }) + it('getDevices', async () => { + // expect(getDevices([[{ name: "", version: "" }]], [])).toBe(undefined); + // expect(getDevices(undefined, [])).toBe(undefined); + }) + it('onSelectionChanged', async () => { + expect(onSelectionChanged({ current: { api: { getSelectedRows() { } } } }, jest.fn())).toBe(undefined); + expect(onSelectionChanged({ current: { api: { getSelectedRows() { return [{ country: "DE" }] } } } }, jest.fn())).toBe(undefined); + }) + it('selectedCheckboxFunction', async () => { + expect(selectedCheckboxFunction([{ vin: "" }], { current: { api: { forEachNode() { return [{ data: { vin: "" } }] } } } })).toBe(undefined); + }) + it('tests getUploadFormDataForNewComponent', () => { + let result = getUploadFormDataForNewComponent({}, [], '', '', '', '', ''); + expect(formData).toEqual(new FormData()); + }) + it('tests onClickMenuItem', () => { + expect(onClickMenuItem(jest.fn())).toBe(undefined); + }) + it('tests onCompletedCreateTrack', () => { + expect(onCompletedCreateTrack(jest.fn(), jest.fn(), jest.fn(), { data: { createTrack: { id: 'test' } } }, true)).toBe(undefined); + expect(onCompletedCreateTrack(jest.fn(), jest.fn(), jest.fn(), '', true)).toBe(undefined); + expect(onCompletedCreateTrack(jest.fn(), jest.fn(), jest.fn(), { errors: [{ message: 'test' }] }, false)).toBe(undefined); + expect(onCompletedCreateTrack(jest.fn(), jest.fn(), jest.fn(), '', false)).toBe(undefined); + jest.runAllTimers(); + expect(window.performance.mark).toBe(undefined); + const useRouter = jest.spyOn(require('next/router'), 'useRouter'); + useRouter.mockImplementation(() => ({ + pathname: '/', + })); + }) + beforeEach(() => { + fetch.resetMocks(); + }); + store.getActions().setTname('Track'); + store.getActions().setTid(0); + store.getActions().setCompid(1234); + + it('tests onClickNewTrack', () => { + expect(onClickNewTrack(jest.fn())).toBe(undefined); + jest.runAllTimers(); + }) + + +}) + diff --git a/developer-console-ui/app/__tests__/shared/ActiveLink.test.tsx b/developer-console-ui/app/__tests__/shared/ActiveLink.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f8a0368238f60100599299a471386feb24b50dd4 --- /dev/null +++ b/developer-console-ui/app/__tests__/shared/ActiveLink.test.tsx @@ -0,0 +1,23 @@ +import '@testing-library/jest-dom'; +import { render } from '@testing-library/react'; +import ActiveLink from '../../components/layout/ActiveLink'; +import { MemoryRouterProvider } from 'next-router-mock/MemoryRouterProvider'; +import { StoreProvider } from 'easy-peasy'; +import { store } from '../../services/store.service'; +const props = { + children: {}, + href: "/dco/scenario", +} +describe("ActiveLink function", () => { + it("should render active link", () => { + render( + // @ts-ignore + <StoreProvider store={store}> + <MemoryRouterProvider url="/dco"> + <ActiveLink href={props.href}></ActiveLink> + </MemoryRouterProvider> + </StoreProvider> + ); + + }); +}); diff --git a/developer-console-ui/app/__tests__/shared/boxToast.test.tsx b/developer-console-ui/app/__tests__/shared/boxToast.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..937eea32bb8acafb9eb104f594c838fc3645f62b --- /dev/null +++ b/developer-console-ui/app/__tests__/shared/boxToast.test.tsx @@ -0,0 +1,78 @@ +import '@testing-library/jest-dom'; +import { render } from '@testing-library/react'; +import { StoreProvider } from 'easy-peasy'; +import { store } from '../../services/store.service'; +import BoxToast from '../../components/layout/boxToast'; +import SuccessToast from '../../components/layout/successToast'; +import ErrorToast from '../../components/layout/errorToast'; +const props = { + toastMsg: "Package has been created successfully" +} +const propsError = { + toastMsg: "There is an error" +} +const includes = props.toastMsg?.includes("has been deleted successfully"); +const status = (props.toastMsg == "Package has been created successfully" || props.toastMsg == "Track has been added succesfully" || includes) + +describe("Common toast message", () => { + it("should render toast message: Package has been created successfully", () => { + if (status == true) { + render( + // @ts-ignore + <StoreProvider store={store}> + <BoxToast toastMsg={props.toastMsg}></BoxToast> + </StoreProvider> + ); + render( + // @ts-ignore + <StoreProvider store={store}> + <SuccessToast toastMsg={props.toastMsg}></SuccessToast> + </StoreProvider> + ); + } + else { + render( + // @ts-ignore + <StoreProvider store={store}> + <BoxToast toastMsg={props.toastMsg}></BoxToast> + </StoreProvider> + ); + render( + // @ts-ignore + <StoreProvider store={store}> + <ErrorToast toastMsg={props.toastMsg}></ErrorToast> + </StoreProvider> + ); + } + }); + it("should render toast message: There is an error", () => { + if (status == true) { + render( + // @ts-ignore + <StoreProvider store={store}> + <BoxToast toastMsg={propsError.toastMsg}></BoxToast> + </StoreProvider> + ); + render( + // @ts-ignore + <StoreProvider store={store}> + <SuccessToast toastMsg={propsError.toastMsg}></SuccessToast> + </StoreProvider> + ); + } + else { + render( + // @ts-ignore + <StoreProvider store={store}> + <BoxToast toastMsg={propsError.toastMsg}></BoxToast> + </StoreProvider> + ); + render( + // @ts-ignore + <StoreProvider store={store}> + <ErrorToast toastMsg={propsError.toastMsg}></ErrorToast> + </StoreProvider> + ); + } + }); +}); diff --git a/developer-console-ui/app/__tests__/shared/conditionalAlertBtn.test.tsx b/developer-console-ui/app/__tests__/shared/conditionalAlertBtn.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..249391860f1fd37d64eb9685c221cfe52e4de31d --- /dev/null +++ b/developer-console-ui/app/__tests__/shared/conditionalAlertBtn.test.tsx @@ -0,0 +1,29 @@ +import { fireEvent, render } from '@testing-library/react'; +import { StoreProvider } from 'easy-peasy'; +import { store } from '../../services/store.service'; +import ConditionalAlertBtn from '../../pages/shared/conditionalAlertBtn'; +describe('conditionalAlertBtn', () => { + const props = { + show: true, + popupState: '', + popupMsg: '', + no: '', + onClose: jest.fn(), + respectiveFun: jest.fn(), + yes: '' + } + test('conditionalAlertBtn render', async () => { + const component = render( + // @ts-ignore + <StoreProvider store={store}> + <ConditionalAlertBtn show="true" onClose={jest.fn()} respectiveFun={jest.fn()} props={props} /> + </StoreProvider> + ) + const closeAlert = component.getByTestId("closeAlert"); + fireEvent.click(closeAlert); + const closeAlert1 = component.getByTestId("closeAlert1"); + fireEvent.click(closeAlert1); + const deleteTrack = component.getByTestId("deleteTrack"); + fireEvent.click(deleteTrack); + }); +}) \ No newline at end of file diff --git a/developer-console-ui/app/__tests__/shared/counterWithToolTip.test.tsx b/developer-console-ui/app/__tests__/shared/counterWithToolTip.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..111dd5728d67806e8835aae60846a40cbce25465 --- /dev/null +++ b/developer-console-ui/app/__tests__/shared/counterWithToolTip.test.tsx @@ -0,0 +1,34 @@ +import { enableFetchMocks } from 'jest-fetch-mock' +enableFetchMocks() +jest.mock('next/router', () => require('next-router-mock')); +import fetch from 'jest-fetch-mock' +import { StoreProvider } from 'easy-peasy'; +import { store } from '../../services/store.service'; +import CounterWithToolTip from '../../pages/shared/counterWithToolTip'; +import { render } from '@testing-library/react'; + +describe('CounterWithToolTip Page', () => { + const mockPerformanceMark = jest.fn(); + window.performance.mark = mockPerformanceMark; + jest.useFakeTimers(); + beforeEach(() => { + fetch.resetMocks(); + }); + + test('add CounterWithToolTip page render', async () => { + render( + // @ts-ignore + <StoreProvider store={store}> + <CounterWithToolTip toolTipVal={["1"]} /> + </StoreProvider> + ) + }); + test('add CounterWithToolTip page render', async () => { + render( + // @ts-ignore + <StoreProvider store={store}> + <CounterWithToolTip toolTipVal={["1", "2"]} /> + </StoreProvider> + ) + }); +}) diff --git a/developer-console-ui/app/__tests__/shared/pagination.test.tsx b/developer-console-ui/app/__tests__/shared/pagination.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..dae5a3f5f343ccba20553cf76c06b1e511439cf4 --- /dev/null +++ b/developer-console-ui/app/__tests__/shared/pagination.test.tsx @@ -0,0 +1,25 @@ +import '@testing-library/jest-dom'; +import { fireEvent, render ,screen} from '@testing-library/react'; +import { MemoryRouterProvider } from 'next-router-mock/MemoryRouterProvider'; +import { StoreProvider } from 'easy-peasy'; +import { store } from '../../services/store.service'; +import Pagination from '../../pages/shared/paginationTable'; +const props = { + pageChangeHandler:jest.fn(), totalRows:20, rowsPerPage:10 +} +describe("pagination function", () => { + it("should render pagination", () => { + render( + // @ts-ignore + <StoreProvider store={store}> + <MemoryRouterProvider url="/dco"> + <Pagination {...props}/> + </MemoryRouterProvider> + </StoreProvider> + ); + const checkBtn1 = screen.getByTestId("btn1"); + fireEvent.click(checkBtn1); + const checkBtn = screen.getByTestId("btn"); + fireEvent.click(checkBtn); + }); +}); diff --git a/developer-console-ui/app/__tests__/shared/status.test.tsx b/developer-console-ui/app/__tests__/shared/status.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..6245b947b4e4acafbd9f8d999f115e3606d07348 --- /dev/null +++ b/developer-console-ui/app/__tests__/shared/status.test.tsx @@ -0,0 +1,77 @@ +import { render } from '@testing-library/react'; +import { StoreProvider } from 'easy-peasy'; +import { Status } from '../../pages/shared/status'; +import { store } from '../../services/store.service'; +describe('Status Page', () => { + test('statuses of vehicles in track add and details page (Vehicle DDetails-VD)', async () => { + render( + // @ts-ignore + <StoreProvider store={store}> + <Status status={'Testing'} type={'VD'} /> + </StoreProvider> + ) + render( + // @ts-ignore + <StoreProvider store={store}> + <Status status={'READY'} type={'VD'} /> + </StoreProvider> + ) + render( + // @ts-ignore + <StoreProvider store={store}> + <Status status={'DRAFT'} type={'VD'} /> + </StoreProvider> + ) + render( + // @ts-ignore + <StoreProvider store={store}> + <Status status={'TEST'} type={'VD'} /> + </StoreProvider> + ) + render( + // @ts-ignore + <StoreProvider store={store}> + <Status status={'SUSPEND'} type={'VD'} /> + </StoreProvider> + ) + render( + // @ts-ignore + <StoreProvider store={store}> + <Status status={'PRODUCTION'} type={'VD'} /> + </StoreProvider> + ) + }); + + test('status of simulations for simulation listing page(Simulation Status-SS)', async () => { + render( + // @ts-ignore + <StoreProvider store={store}> + <Status status={'Done'} type={'SS'} /> + </StoreProvider> + ) + render( + // @ts-ignore + <StoreProvider store={store}> + <Status status={'Pending'} type={'SS'} /> + </StoreProvider> + ) + render( + // @ts-ignore + <StoreProvider store={store}> + <Status status={'Running'} type={'SS'} /> + </StoreProvider> + ) + render( + // @ts-ignore + <StoreProvider store={store}> + <Status status={'Timeout'} type={'SS'} /> + </StoreProvider> + ) + render( + // @ts-ignore + <StoreProvider store={store}> + <Status status={'Error'} type={'SS'} /> + </StoreProvider> + ) + }); +}) diff --git a/developer-console-ui/app/__tests__/shared/tagLists.test.tsx b/developer-console-ui/app/__tests__/shared/tagLists.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b26a1060f97448ebb7327299fb6d7593b24a5b4d --- /dev/null +++ b/developer-console-ui/app/__tests__/shared/tagLists.test.tsx @@ -0,0 +1,51 @@ +import { render } from '@testing-library/react'; +import { StoreProvider } from 'easy-peasy'; +import { store } from '../../services/store.service'; +import TagLists from '../../pages/shared/tagLists'; +describe('TagLists Page', () => { + + test('TagLists page render', async () => { + render( + // @ts-ignore + <StoreProvider store={store}> + <TagLists data={{ getReleaseById: { countries: ['IND'] } }} type={'countries'} from={'relaseDetails'} /> + </StoreProvider> + ) + render( + // @ts-ignore + <StoreProvider store={store}> + <TagLists data={{ getReleaseById: { brands: ['Test'] } }} type={'brands'} from={'relaseDetails'} /> + </StoreProvider> + ) + render( + // @ts-ignore + <StoreProvider store={store}> + <TagLists data={{ getReleaseById: { models: ['Test'] } }} type={'models'} from={'relaseDetails'} /> + </StoreProvider> + ) + render( + // @ts-ignore + <StoreProvider store={store}> + <TagLists data={['test']} type={'countries'} from={'trackDetails'} /> + </StoreProvider> + ) + render( + // @ts-ignore + <StoreProvider store={store}> + <TagLists data={['test']} type={'brands'} from={'trackDetails'} /> + </StoreProvider> + ) + render( + // @ts-ignore + <StoreProvider store={store}> + <TagLists data={[{ name: 'test', version: '1.1.1' }]} type={'compNVersion'} from={'trackDetails'} /> + </StoreProvider> + ) + render( + // @ts-ignore + <StoreProvider store={store}> + <TagLists data={['test']} type={'devices'} from={'trackDetails'} /> + </StoreProvider> + ) + }); +}) diff --git a/developer-console-ui/app/__tests__/simulation/simulation.test.tsx b/developer-console-ui/app/__tests__/simulation/simulation.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..88919f9549533991fbea1d10157c43d3013715ac --- /dev/null +++ b/developer-console-ui/app/__tests__/simulation/simulation.test.tsx @@ -0,0 +1,53 @@ +import { act, render } from '@testing-library/react'; +import { StoreProvider } from 'easy-peasy'; +import { MockedProvider } from "@apollo/react-testing"; +import { store } from '../../services/store.service'; +import { MemoryRouterProvider } from 'next-router-mock/dist/MemoryRouterProvider'; +import { enableFetchMocks } from 'jest-fetch-mock' +enableFetchMocks() +jest.mock('next/router', () => require('next-router-mock')); +import fetch from 'jest-fetch-mock' +import { GET_SIMULATIONS } from '../../services/queries'; +import { gql } from '@apollo/client'; +import Simulation from '../../pages/dco/simulation'; +describe('Table render in scenario', () => { + const useRouter = jest.spyOn(require('next/router'), 'useRouter'); + useRouter.mockImplementation(() => ({ + pathname: '/dco/scenario', + })); + const mockList = [{ + request: { + query: gql(GET_SIMULATIONS), + variables: { + search: null, + query: null, + page: 1, + size: 10, + sort: null, + } + }, + result: { data: { simulationReadByQuery: {pages:1,total:2} } }, + }] + test('table with props', async () => { + store.getActions().setCount(0); + useRouter + render( + // @ts-ignore + <StoreProvider store={store}> + <MemoryRouterProvider url="/dco/scenario"> + <MockedProvider mocks={mockList}> + <Simulation /> + </MockedProvider> + </MemoryRouterProvider> + </StoreProvider> + ) + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 0)); + }); + store.getActions().setInvert(true); + }); + beforeEach(() => { + fetch.resetMocks(); + }); + +}) diff --git a/developer-console-ui/app/__tests__/tracksMain/trackInfo/trackDetails.test.tsx b/developer-console-ui/app/__tests__/tracksMain/trackInfo/trackDetails.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3c070aa217b4f8f7519fbbf8e53837e757f741a1 --- /dev/null +++ b/developer-console-ui/app/__tests__/tracksMain/trackInfo/trackDetails.test.tsx @@ -0,0 +1,53 @@ +import { render } from '@testing-library/react'; +import { MemoryRouterProvider } from 'next-router-mock/dist/MemoryRouterProvider'; +import { StoreProvider } from 'easy-peasy'; +import { enableFetchMocks } from 'jest-fetch-mock' +enableFetchMocks() +jest.mock('next/router', () => require('next-router-mock')); +import fetch from 'jest-fetch-mock' +import { store } from '../../../services/store.service'; +import { MockedProvider } from '@apollo/react-testing'; +import { TRACK_DETAILS } from '../../../services/queries'; +import TrackDetails from '../../../pages/dco/tracksMain/trackInfo/trackDetails'; + +describe('Track info page', () => { + const useRouter = jest.spyOn(require('next/router'), 'useRouter'); + useRouter.mockImplementation(() => ({ + pathname: '/dco/tracksMain/trackInfo', + })); + const input = { + description: "", + duration: "7", + id: "ab2ce5db-a0ad-4b6b-a16f-7ba5760c9f06", + name: "track", + state: "CREATED", + trackType: "Test" + } + const mockList = [{ + request: { + query: TRACK_DETAILS, + variables: { id: '' } + }, + result: { data: { findTrackById: input } }, + }] + + test('track info render', () => { + store.getActions().setCount(0); + useRouter + render( + // @ts-ignore + <StoreProvider store={store}> + <MemoryRouterProvider url="/dco/tracksMain/trackInfo/trackVehicleDetails"> + {/* <Dco> */} + <MockedProvider mocks={mockList}> + <TrackDetails /> + </MockedProvider> + {/* </Dco> */} + </MemoryRouterProvider> + </StoreProvider> + ) + }); + beforeEach(() => { + fetch.resetMocks(); + }); +}) \ No newline at end of file diff --git a/developer-console-ui/app/__tests__/tracksMain/trackInfo/trackInfo.test.tsx b/developer-console-ui/app/__tests__/tracksMain/trackInfo/trackInfo.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8600c1877ff2e81295a53a55708e253525e67e3d --- /dev/null +++ b/developer-console-ui/app/__tests__/tracksMain/trackInfo/trackInfo.test.tsx @@ -0,0 +1,114 @@ +import { act, fireEvent, render, screen } from '@testing-library/react'; +import { MemoryRouterProvider } from 'next-router-mock/dist/MemoryRouterProvider'; +import { StoreProvider } from 'easy-peasy'; +import { enableFetchMocks } from 'jest-fetch-mock' +enableFetchMocks() +jest.mock('next/router', () => require('next-router-mock')); +import fetch from 'jest-fetch-mock' +import TrackInfo from '../../../pages/dco/tracksMain/trackInfo'; +import { store } from '../../../services/store.service'; +import { MockedProvider } from '@apollo/react-testing'; +import { TRACK_DETAILS } from '../../../services/queries'; +import TracksMain from '../../../pages/dco/tracksMain'; +import Login from '../../../pages/login'; + +describe('Track info page', () => { + const useRouter = jest.spyOn(require('next/router'), 'useRouter'); + useRouter.mockImplementation(() => ({ + pathname: '/dco/tracksMain/trackInfo/trackVehicleDetails', + })); + const router = { + push: jest.fn(), + replace: jest.fn(), + go: jest.fn(), + createHref: jest.fn(), + createLocation: jest.fn(), + isActive: jest.fn(), + matcher: { + match: jest.fn(), + getRoutes: jest.fn(), + isActive: jest.fn(), + format: jest.fn() + }, + addTransitionHook: jest.fn() + }; + const input = { + description: "", + duration: "7", + id: "ab2ce5db-a0ad-4b6b-a16f-7ba5760c9f06", + name: "track", + state: "CREATED", + trackType: "Test" + } + const mockList = [{ + request: { + query: TRACK_DETAILS, + variables: { id: '' } + }, + result: { data: { findTrackById: input } }, + }] + test('', () => { + router.replace('/login') + render( + // @ts-ignore + <StoreProvider store={store}> + <MemoryRouterProvider url="/dco/tracksMain/trackInfo/trackVehicleDetails"> + {/* <Dco> */} + <MockedProvider mocks={mockList}> + <Login /> + </MockedProvider> + {/* </Dco> */} + </MemoryRouterProvider> + </StoreProvider> + ) + }) + test('track info render', () => { + store.getActions().setCount(0); + useRouter + render( + // @ts-ignore + <StoreProvider store={store}> + <MemoryRouterProvider url="/dco/tracksMain/trackInfo/trackVehicleDetails"> + {/* <Dco> */} + <MockedProvider mocks={mockList}> + <TrackInfo /> + </MockedProvider> + {/* </Dco> */} + </MemoryRouterProvider> + </StoreProvider> + ) + }); + test('table with props bakbutton', async () => { + store.getActions().setCount(0); + useRouter + render( + // @ts-ignore + <StoreProvider store={store}> + <MemoryRouterProvider url="/dco/tracksMain/trackInfo/trackVehicleDetails"> + <MockedProvider mocks={mockList}> + <TrackInfo /> + </MockedProvider> + </MemoryRouterProvider> + </StoreProvider> + ) + const checkBtn = screen.getByTestId("backButton"); + fireEvent.click(checkBtn); + useRouter + render( + // @ts-ignore + <StoreProvider store={store}> + <MemoryRouterProvider url="/dco/tracksMain"> + <MockedProvider> + <TracksMain /> + </MockedProvider> + </MemoryRouterProvider> + </StoreProvider> + ) + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 0)); + }); + }); + beforeEach(() => { + fetch.resetMocks(); + }); +}) \ No newline at end of file diff --git a/developer-console-ui/app/__tests__/tracksMain/trackInfo/trackVehicleDetails.test.tsx b/developer-console-ui/app/__tests__/tracksMain/trackInfo/trackVehicleDetails.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..0cbf2102dc4de5515ce3934c51f16674772033b9 --- /dev/null +++ b/developer-console-ui/app/__tests__/tracksMain/trackInfo/trackVehicleDetails.test.tsx @@ -0,0 +1,75 @@ +import { render } from '@testing-library/react'; +import { MemoryRouterProvider } from 'next-router-mock/dist/MemoryRouterProvider'; +import { StoreProvider } from 'easy-peasy'; +import { enableFetchMocks } from 'jest-fetch-mock' +enableFetchMocks() +jest.mock('next/router', () => require('next-router-mock')); +import fetch from 'jest-fetch-mock' +import TrackInfo from '../../../pages/dco/tracksMain/trackInfo'; +import { store } from '../../../services/store.service'; +import { MockedProvider } from '@apollo/react-testing'; +import { TRACK_DETAILS } from '../../../services/queries'; +import TrackDetails from '../../../pages/dco/tracksMain/trackInfo/trackDetails'; +import TrackVehicleDetails from '../../../pages/dco/tracksMain/trackInfo/trackVehicleDetails'; + +describe('Track info page', () => { + const useRouter = jest.spyOn(require('next/router'), 'useRouter'); + useRouter.mockImplementation(() => ({ + pathname: '/dco/tracksMain/trackInfo', + })); + const input = { + description: "", + duration: "7", + id: "ab2ce5db-a0ad-4b6b-a16f-7ba5760c9f06", + name: "track", + state: "CREATED", + trackType: "Test", + vehicles: [{ + brand: "Mercedes-Benz", + country: "FR", + devices: [{ + components: [], + createdAt: "2022-12-15T21:30:46.443445", + dmProtocol: "LWM2M", + dmProtocolVersion: "1.0.2", + gatewayId: "", + id: "84c32703-982a-4efd-920b-b80977073b33", + modelType: "HIGH_EU", + modifiedAt: "2022-12-20T14:09:25.437637", + serialNumber: null, + status: "ACTIVE", + type: "TCU" + }], + status: "DRAFT", + updatedAt: "2023-02-22T00:58:01.734361", + vin: "BBTEST00000000340" + }] + } + const mockList = [{ + request: { + query: TRACK_DETAILS, + variables: { id: '' } + }, + result: { data: { findTrackById: input } }, + }] + + test('track info render', () => { + store.getActions().setCount(0); + useRouter + render( + // @ts-ignore + <StoreProvider store={store}> + <MemoryRouterProvider url="/dco/tracksMain/trackInfo/trackVehicleDetails"> + {/* <Dco> */} + <MockedProvider mocks={mockList}> + <TrackVehicleDetails /> + </MockedProvider> + {/* </Dco> */} + </MemoryRouterProvider> + </StoreProvider> + ) + }); + beforeEach(() => { + fetch.resetMocks(); + }); +}) \ No newline at end of file diff --git a/developer-console-ui/app/__tests__/tracksMain/trackList.test.tsx b/developer-console-ui/app/__tests__/tracksMain/trackList.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..57691aeb054dd0240b6d5625d92ab315412ce617 --- /dev/null +++ b/developer-console-ui/app/__tests__/tracksMain/trackList.test.tsx @@ -0,0 +1,71 @@ +import { act, render } from '@testing-library/react'; +import { StoreProvider } from 'easy-peasy'; +import { MockedProvider } from "@apollo/react-testing"; +import { store } from '../../services/store.service'; +import { MemoryRouterProvider } from 'next-router-mock/dist/MemoryRouterProvider'; +import { enableFetchMocks } from 'jest-fetch-mock' +enableFetchMocks() +jest.mock('next/router', () => require('next-router-mock')); +import fetch from 'jest-fetch-mock' +import { LIST_TRACKS } from '../../services/queries'; +import { gql } from '@apollo/client'; +import TrackList from '../../pages/dco/tracksMain/trackList'; +import ConditionalAlertBtn from '../../pages/shared/conditionalAlertBtn'; +import { DELETE_SCENARIO } from '../../services/functionScenario.services'; +describe('Table render in scenario', () => { + const useRouter = jest.spyOn(require('next/router'), 'useRouter'); + useRouter.mockImplementation(() => ({ + pathname: '/dco/scenario', + })); + const mockList = [{ + request: { + query: gql(LIST_TRACKS), + variables: { trackPattern: '', page: 1, size: 10 } + }, + result: { data: { searchTrackByPattern: '' } }, + }] + const mockListDel = [{ + request: { + query: DELETE_SCENARIO, + variables: { id: '' } + }, + result: { data: { deleteTrack: "Track deleted" } }, + }] + const props = { path: 'sim' } + test('table with props', async () => { + store.getActions().setCount(0); + useRouter + render( + // @ts-ignore + <StoreProvider store={store}> + <MemoryRouterProvider url="/dco/scenario"> + <MockedProvider mocks={mockList}> + <TrackList {...props} /> + <ConditionalAlertBtn></ConditionalAlertBtn> + </MockedProvider> + </MemoryRouterProvider> + </StoreProvider> + ) + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 0)); + }); + store.getActions().setInvert(true); + }); + + test('delete track render', () => { + render( + // @ts-ignore + <StoreProvider store={store}> + <MemoryRouterProvider url="/dco/track"> + <MockedProvider mocks={mockListDel}> + <TrackList {...props} /> + </MockedProvider> + </MemoryRouterProvider> + </StoreProvider> + ) + }); + beforeEach(() => { + fetch.resetMocks(); + }); + +}) diff --git a/developer-console-ui/app/__tests__/tracksMain/tracksMain.test.tsx b/developer-console-ui/app/__tests__/tracksMain/tracksMain.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..6bc904d7a4484962fb5fc49590cd8cc98f3e7f0d --- /dev/null +++ b/developer-console-ui/app/__tests__/tracksMain/tracksMain.test.tsx @@ -0,0 +1,38 @@ +import { act, render } from '@testing-library/react'; +import { StoreProvider } from 'easy-peasy'; +import { MockedProvider } from "@apollo/react-testing"; +import { store } from '../../services/store.service'; +import { MemoryRouterProvider } from 'next-router-mock/dist/MemoryRouterProvider'; +import { enableFetchMocks } from 'jest-fetch-mock' +enableFetchMocks() +jest.mock('next/router', () => require('next-router-mock')); +import fetch from 'jest-fetch-mock' +import TracksMain from '../../pages/dco/tracksMain'; +describe('Table render in scenario', () => { + const useRouter = jest.spyOn(require('next/router'), 'useRouter'); + useRouter.mockImplementation(() => ({ + pathname: '/dco/tracksMain', + })); + test('table with props', async () => { + store.getActions().setCount(0); + useRouter + render( + // @ts-ignore + <StoreProvider store={store}> + <MemoryRouterProvider url="/dco/tracksMain"> + <MockedProvider> + <TracksMain /> + </MockedProvider> + </MemoryRouterProvider> + </StoreProvider> + ) + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 0)); + }); + store.getActions().setInvert(true); + }); + beforeEach(() => { + fetch.resetMocks(); + }); + +}) diff --git a/developer-console-ui/app/assets/constants/constants.js b/developer-console-ui/app/assets/constants/constants.js new file mode 100644 index 0000000000000000000000000000000000000000..e6a09ed48d3af04d541b566837aab2a8e2066499 --- /dev/null +++ b/developer-console-ui/app/assets/constants/constants.js @@ -0,0 +1,2 @@ +// export const GRAPHQL_API = "/graphql"; +export const GRAPHQL_API = process.env.APP_DCO_GATEWAY_SERVICE_URL; \ No newline at end of file diff --git a/developer-console-ui/app/components/auth/ForceSession.tsx b/developer-console-ui/app/components/auth/ForceSession.tsx new file mode 100644 index 0000000000000000000000000000000000000000..665d7a6310859b20e5ac6451c684453cbde0aba6 --- /dev/null +++ b/developer-console-ui/app/components/auth/ForceSession.tsx @@ -0,0 +1,19 @@ +import { usePortalOutlet } from '@dco/sdv-ui'; +import { useEffect, useState } from 'react' + +type Props = { + children: any +} +const ForceSession = ({ children }: Props) => { + const outlet = usePortalOutlet(); + const [activated, setActivated] = useState(false); + useEffect(() => { + setActivated(true); + }, []); + if (outlet) { + return children + } + return null +} + +export default ForceSession diff --git a/developer-console-ui/app/components/layout/ActiveLink.tsx b/developer-console-ui/app/components/layout/ActiveLink.tsx new file mode 100644 index 0000000000000000000000000000000000000000..efda577be2e5706df3ce85bb21c4e87b648ac5a9 --- /dev/null +++ b/developer-console-ui/app/components/layout/ActiveLink.tsx @@ -0,0 +1,16 @@ +import '@dco/sdv-ui/dist/assets/main.css' +import React from "react"; +import { useRouter } from "next/router"; +import Link, { LinkProps } from 'next/link'; + +function ActiveLink(props: React.PropsWithChildren<LinkProps>) { + const router = useRouter() + return React.isValidElement(props.children) ? ( + <Link {...props} passHref> + {React.cloneElement(props.children, { + active: router?.pathname === props.href || router?.pathname === props.as + })} + </Link> + ) : null; +} +export default ActiveLink; \ No newline at end of file diff --git a/developer-console-ui/app/components/layout/boxToast.tsx b/developer-console-ui/app/components/layout/boxToast.tsx new file mode 100644 index 0000000000000000000000000000000000000000..823390fc870a1b985260cf4740f43f4fa4916ee8 --- /dev/null +++ b/developer-console-ui/app/components/layout/boxToast.tsx @@ -0,0 +1,21 @@ +import { Box } from "@dco/sdv-ui"; +import { BoxToastProps } from "../../types"; +import ErrorToast from "./errorToast"; +import SuccessToast from "./successToast"; + +export const BoxToast = ({ toastMsg }: BoxToastProps) => { + const status = (toastMsg == "Package has been created successfully" || toastMsg == "Track has been added successfully" || toastMsg == "Component has been created successfully" || toastMsg?.includes("has been deleted successfully") || toastMsg == 'Track deleted' || toastMsg == 'Workflow creation window open successfully' || toastMsg == 'Campaign Restarted Successfully' || toastMsg == "Scenario has been created successfully" ||toastMsg =="Scenario has been updated successfully"|| toastMsg?.includes("Scenario deleted")||toastMsg=='Simulation has been launched successfully') + return ( + <Box + elevation="medium" + padding="small" + variant="high" + radius="round" + style={{ background: 'rgba(2, 14, 20, 1)' }} + > + {status && <SuccessToast toastMsg={toastMsg}></SuccessToast>} + {!status && <ErrorToast toastMsg={toastMsg}></ErrorToast>} + </Box> + ) +} +export default BoxToast; \ No newline at end of file diff --git a/developer-console-ui/app/components/layout/errorToast.tsx b/developer-console-ui/app/components/layout/errorToast.tsx new file mode 100644 index 0000000000000000000000000000000000000000..575b2a88673e6eda41c1cb99e825dcec6964f4f8 --- /dev/null +++ b/developer-console-ui/app/components/layout/errorToast.tsx @@ -0,0 +1,31 @@ +import { Button, Flex, FlexItem, Icon } from "@dco/sdv-ui"; +import { BoxToastProps } from "../../types"; + +export const ErrorToast = ({toastMsg}:BoxToastProps) => { + return ( + <div> + <Flex + gutters="small" + valign="center" + className='error' + > + <FlexItem autoSize> + <Icon name="status-error-stop" /> + </FlexItem> + <FlexItem> + {toastMsg} + </FlexItem> + <FlexItem autoSize> + <Button + round + variant="naked" + data-testid="toastBtn"> + <Icon name="remove-close" /> + </Button> + </FlexItem> + </Flex> + </div> + + ) +} +export default ErrorToast; \ No newline at end of file diff --git a/developer-console-ui/app/components/layout/layout.tsx b/developer-console-ui/app/components/layout/layout.tsx new file mode 100644 index 0000000000000000000000000000000000000000..110792641be920c3dbab39688376cf9583c09c8a --- /dev/null +++ b/developer-console-ui/app/components/layout/layout.tsx @@ -0,0 +1,77 @@ +import { Box, Flex, Icon, Menu, NavigationBar, NavigationBarItem } from '@dco/sdv-ui' +import { useStoreActions, useStoreState } from 'easy-peasy' +import { useRouter } from 'next/router' +import React, { useEffect, useState } from 'react' +import ActiveLink from './ActiveLink' +export function User() { + return useStoreState((state: any) => state.user) +} +export function Layout({ children }: any) { + const router = useRouter() + const invert = useStoreState((state: any) => state.invert) + const pathname = router?.pathname + const token = localStorage.getItem('token'); + const setUser = useStoreActions((actions: any) => actions.setUser) + const [openMenu, setOpenMenu] = useState(false) + const [target, setTarget]: any = useState(null); + useEffect(() => { + if (pathname === '/') { + router.replace('/login') + } else if (pathname === '/dco') { + if (token) { + router.replace('/dco/scenario') + } else { router.push('login') } + } + }) + const username = localStorage.getItem('user') + const user = User(); + return ( + <Box fullHeight> + <Flex column fullHeight> + <Flex.Item autoSize > + <Box fullHeight padding='none' invert={invert} variant='deep'> + <NavigationBar> + <ActiveLink href={router?.pathname.includes('/dco/') ? pathname : '/dco/scenario'}> + <NavigationBarItem logo> + <Icon data-testid='logo' name='tbbui' + /> SDV Developer + Console + </NavigationBarItem> + </ActiveLink> + {(user && token) ? <NavigationBarItem + noLink ref={setTarget} + right data-testid='logout' + onClick={() => { setOpenMenu(true) }} + > + {username} <Icon name='user' id='Logout' /> + </NavigationBarItem> : ''} + + </NavigationBar> + </Box> + </Flex.Item> + <Flex.Item> + <Flex fullHeight> + <Flex.Item> + <Box fullHeight> + {children} + </Box> + </Flex.Item> + </Flex> + </Flex.Item> + </Flex> + <Menu + open={openMenu} + target={target} + items={[{ text: 'logout' }]} + onItemClick={(e: any) => { + localStorage.removeItem('token') + setUser(false) + router.replace('/logout') + }} + onHide={() => { setOpenMenu(false); }} + /> + </Box> + + ) +} +export default Layout diff --git a/developer-console-ui/app/components/layout/successToast.tsx b/developer-console-ui/app/components/layout/successToast.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f468fabc13aee49fc576c9c8a0d1510ae0c4ab99 --- /dev/null +++ b/developer-console-ui/app/components/layout/successToast.tsx @@ -0,0 +1,31 @@ +import { Button, Flex, FlexItem, Icon } from "@dco/sdv-ui"; +import { BoxToastProps } from "../../types"; + +export const SuccessToast = ({toastMsg}:BoxToastProps) => { + return ( + <div> + <Flex + gutters="small" + valign="center" + className='success' + > + <FlexItem autoSize> + <Icon name="status-ok-check" /> + </FlexItem> + <FlexItem> + {toastMsg} + </FlexItem> + <FlexItem autoSize> + <Button + round + variant="naked" + data-testid="toastBtn"> + <Icon name="remove-close" /> + </Button> + </FlexItem> + </Flex> + </div> + + ) +} +export default SuccessToast; \ No newline at end of file diff --git a/developer-console-ui/app/dco-sdv-ui-1.120.0.tgz b/developer-console-ui/app/dco-sdv-ui-1.120.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..d4248fc5bd3b237e89c3d6eb39652dc2b1994d45 Binary files /dev/null and b/developer-console-ui/app/dco-sdv-ui-1.120.0.tgz differ diff --git a/developer-console-ui/app/entrypoint.sh b/developer-console-ui/app/entrypoint.sh new file mode 100644 index 0000000000000000000000000000000000000000..73444b7acec30c93b2439237cfdbe7bdf5232bc7 --- /dev/null +++ b/developer-console-ui/app/entrypoint.sh @@ -0,0 +1,18 @@ +#!/bin/bash -e + +set -e +set +x + +[[ "${APP_NAME}" ]] && \ +[[ "${VAULT_ADDR}" ]] && \ +[[ "${VAULT_TOKEN}" ]] && \ +[[ "${VAULT_MOUNT}" ]] && { + VAULT_OAUTH2_ISSUER=$(vault kv get -field app.oauth2.issuer "${VAULT_MOUNT}/${APP_NAME}/oauth2" || echo "") + [[ "${VAULT_OAUTH2_ISSUER}" ]] && export APP_OAUTH2_ISSUER="${VAULT_OAUTH2_ISSUER}" + VAULT_OAUTH2_CLIENT_ID=$(vault kv get -field app.oauth2.client.id "${VAULT_MOUNT}/${APP_NAME}/oauth2" || echo "") + [[ "${VAULT_OAUTH2_CLIENT_ID}" ]] && export APP_OAUTH2_CLIENT_ID="${VAULT_OAUTH2_CLIENT_ID}" + VAULT_OAUTH2_CLIENT_SECRET=$(vault kv get -field app.oauth2.client.secret "${VAULT_MOUNT}/${APP_NAME}/oauth2" || echo "") + [[ "${VAULT_OAUTH2_CLIENT_SECRET}" ]] && export APP_OAUTH2_CLIENT_SECRET="${VAULT_OAUTH2_CLIENT_SECRET}" +} + +exec "${@}" diff --git a/developer-console-ui/app/input/TrackInput.graphqls b/developer-console-ui/app/input/TrackInput.graphqls new file mode 100644 index 0000000000000000000000000000000000000000..7ac9a41abc2ab16ec99e1b04157284a791174efd --- /dev/null +++ b/developer-console-ui/app/input/TrackInput.graphqls @@ -0,0 +1,13 @@ +input TrackInput{ + name: String + duration: String + description: String + state: String + trackType: String + vehicles: [ + { + vin: String + country: String + } + ] + } \ No newline at end of file diff --git a/developer-console-ui/app/input/createComponent.graphqls b/developer-console-ui/app/input/createComponent.graphqls new file mode 100644 index 0000000000000000000000000000000000000000..8b2373434b342f12ad1526024de94f03972a1f79 --- /dev/null +++ b/developer-console-ui/app/input/createComponent.graphqls @@ -0,0 +1,9 @@ +input ComponentInput{ + name: String + type: String + status: [DRAFT,TEST,READY,SUSPEND,PRODUCTION] + version: String + updatedBy: String + targetDevices: [String] + targetHardwareModule: [String] + } \ No newline at end of file diff --git a/developer-console-ui/app/input/createWorkflowDefinition.graphqls b/developer-console-ui/app/input/createWorkflowDefinition.graphqls new file mode 100644 index 0000000000000000000000000000000000000000..d75bc846fd348658d31a484f6746c337c4befe6c --- /dev/null +++ b/developer-console-ui/app/input/createWorkflowDefinition.graphqls @@ -0,0 +1,4 @@ +type WorkflowDefinition { + type: String + namespace: String +} \ No newline at end of file diff --git a/developer-console-ui/app/input/creteRelease.graphqls b/developer-console-ui/app/input/creteRelease.graphqls new file mode 100644 index 0000000000000000000000000000000000000000..6c0b99ec0eb9f4ef8e0ac0c052212c1193c51933 --- /dev/null +++ b/developer-console-ui/app/input/creteRelease.graphqls @@ -0,0 +1,21 @@ +input CreateReleaseRequest{ + releaseId: String + metaTrack: String + isHardwareChangesAllowed: Boolean + brands: [String] + models: [String] + countries: [String] + releaseDate: String + functions: [ + { + name: String + ecu: String + country: String + hardwareModule: String + component:String + componentVersion: String + status: String + lastChange: String + } + ] + } diff --git a/developer-console-ui/app/input/fileUpload.graphqls b/developer-console-ui/app/input/fileUpload.graphqls new file mode 100644 index 0000000000000000000000000000000000000000..fca9ea1fcd4d563dc3c8189d85c32b0a77156f9a --- /dev/null +++ b/developer-console-ui/app/input/fileUpload.graphqls @@ -0,0 +1 @@ +scalar Upload \ No newline at end of file diff --git a/developer-console-ui/app/input/functionName.graphqls b/developer-console-ui/app/input/functionName.graphqls new file mode 100644 index 0000000000000000000000000000000000000000..fdf142d730bb533ac4c8d9596271342303ab949a --- /dev/null +++ b/developer-console-ui/app/input/functionName.graphqls @@ -0,0 +1,5 @@ + inout functionCriteria{ + brands: [String] + models: [String] + countries: [String] + } \ No newline at end of file diff --git a/developer-console-ui/app/input/getCampaignStatistics.graphqls b/developer-console-ui/app/input/getCampaignStatistics.graphqls new file mode 100644 index 0000000000000000000000000000000000000000..8d394dc9c6f2513fe8edbffb27a2d0b909cbe084 --- /dev/null +++ b/developer-console-ui/app/input/getCampaignStatistics.graphqls @@ -0,0 +1,4 @@ +input CampaignStatisticsRequest { + componentId: ID + trackId: ID +} diff --git a/developer-console-ui/app/input/qualityGateApprover.graphqls b/developer-console-ui/app/input/qualityGateApprover.graphqls new file mode 100644 index 0000000000000000000000000000000000000000..88af5bbd6445d31aefe412ed5b153ce2e2a74935 --- /dev/null +++ b/developer-console-ui/app/input/qualityGateApprover.graphqls @@ -0,0 +1,7 @@ +input UpdateApproverStatus{ + releaseId: String + qualityGate: Int + name: String + isApproved: Boolean + status: [PENDING,INPROGRESS,PASSED] + } \ No newline at end of file diff --git a/developer-console-ui/app/input/simulationInput.graphqls b/developer-console-ui/app/input/simulationInput.graphqls new file mode 100644 index 0000000000000000000000000000000000000000..9d723ef68426df96374b6aa0f972636d5c7a7ec0 --- /dev/null +++ b/developer-console-ui/app/input/simulationInput.graphqls @@ -0,0 +1,11 @@ +input SimulationInput { + name: String + environment: String + platform: String + scenarioType: String + hardware: String + description: String + tracks: [ID] + scenarios: [ID] + createdBy: String +} diff --git a/developer-console-ui/app/input/trackUpdate.graphqls b/developer-console-ui/app/input/trackUpdate.graphqls new file mode 100644 index 0000000000000000000000000000000000000000..6f8a6558b3e5834cfed2e6fe9d6084416fc181f0 --- /dev/null +++ b/developer-console-ui/app/input/trackUpdate.graphqls @@ -0,0 +1,5 @@ +input ComponentTrack { + componentId: ID + trackId: ID + trackName:String +} \ No newline at end of file diff --git a/developer-console-ui/app/jest.config.js b/developer-console-ui/app/jest.config.js new file mode 100644 index 0000000000000000000000000000000000000000..5f1df7b908d07146743a1a0439838a3293c3db78 --- /dev/null +++ b/developer-console-ui/app/jest.config.js @@ -0,0 +1,18 @@ +const nextJest = require('next/jest') + +const createJestConfig = nextJest({ + // Provide the path to your Next.js app to load next.config.js and .env files in your test environment + dir: './', +}) + +// Add any custom config to be passed to Jest +const customJestConfig = { + // Add more setup options before each test is run + // setupFilesAfterEnv: ['<rootDir>/jest.setup.js'], + // if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work + moduleDirectories: ['node_modules', '<rootDir>/'], + testEnvironment: 'jest-environment-jsdom', +} + +// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async +module.exports = createJestConfig(customJestConfig) \ No newline at end of file diff --git a/developer-console-ui/app/jest.setup.js b/developer-console-ui/app/jest.setup.js new file mode 100644 index 0000000000000000000000000000000000000000..7452d74569c18adef1f3f35b3ed4873af48992dc --- /dev/null +++ b/developer-console-ui/app/jest.setup.js @@ -0,0 +1,7 @@ + +// Optional: configure or set up a testing framework before each test. +// If you delete this file, remove `setupFilesAfterEnv` from `jest.config.js` + +// Used for __tests__/testing-library.js +// Learn more: https://github.com/testing-library/jest-dom +import '@testing-library/jest-dom/extend-expect' \ No newline at end of file diff --git a/developer-console-ui/app/libs/apollo.ts b/developer-console-ui/app/libs/apollo.ts new file mode 100644 index 0000000000000000000000000000000000000000..54702d22c9a4884146fe8213d19c64a60c9f6bd8 --- /dev/null +++ b/developer-console-ui/app/libs/apollo.ts @@ -0,0 +1,27 @@ +import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client' +import { setContext } from '@apollo/client/link/context'; +import getConfig from 'next/config'; + + +const httpLink = new HttpLink({ uri: getConfig().publicRuntimeConfig.url + '/graphql', fetch: fetch }); + +export const Link = getConfig().publicRuntimeConfig.url + '/graphql' + +const authLink = setContext((_, { headers }) => { + // get the authentication token from local storage if it exists + const token = localStorage.getItem('token'); + // return the headers to the context so httpLink can read them + return { + headers: { + ...headers, + 'Authorization': token ? `Basic ${token}` : "", + } + } +}); + +const client = new ApolloClient({ + link: authLink.concat(httpLink), + cache: new InMemoryCache() +}); + +export default client diff --git a/developer-console-ui/app/libs/config.ts b/developer-console-ui/app/libs/config.ts new file mode 100644 index 0000000000000000000000000000000000000000..2b57b6dc5c024a3fe1d2d102e357feac3fccfb84 --- /dev/null +++ b/developer-console-ui/app/libs/config.ts @@ -0,0 +1,10 @@ +export const app = { + dco: { + gateway: { + service: { + url: process.env.APP_DCO_GATEWAY_SERVICE_URL|| 'http://localhost:8080', + }, + }, + }, + +} diff --git a/developer-console-ui/app/next-env.d.ts b/developer-console-ui/app/next-env.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..4f11a03dc6cc37f2b5105c08f2e7b24c603ab2f4 --- /dev/null +++ b/developer-console-ui/app/next-env.d.ts @@ -0,0 +1,5 @@ +/// <reference types="next" /> +/// <reference types="next/image-types/global" /> + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/developer-console-ui/app/next.config.js b/developer-console-ui/app/next.config.js new file mode 100644 index 0000000000000000000000000000000000000000..aa51bc9083eb81dac3f434fc92846c60c922f761 --- /dev/null +++ b/developer-console-ui/app/next.config.js @@ -0,0 +1,7 @@ +/** @type {import('next').NextConfig} */ +module.exports = { + reactStrictMode: true, + publicRuntimeConfig: { + url: process.env.APP_DCO_GATEWAY_SERVICE_URL, + }, +} diff --git a/developer-console-ui/app/package.json b/developer-console-ui/app/package.json new file mode 100644 index 0000000000000000000000000000000000000000..5fe8c16c5d645f9f77989346bb60305d09b713d3 --- /dev/null +++ b/developer-console-ui/app/package.json @@ -0,0 +1,62 @@ +{ + "name": "app", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "next dev -p ${APP_PORT:-3000}", + "build": "next build", + "start": "next start -p ${APP_PORT:-3000}", + "lint": "next lint", + "sonar": "node sonar.js", + "test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --coverage --watchAll=false --verbose", + "dependency-track": "npm run dependency-track:bom && npm run dependency-track:bom-upload", + "dependency-track:bom": "cyclonedx-node", + "dependency-track:bom-upload": "dependency-track -u bom.xml --dependencyTrackBaseUrl ${DEPENDENCY_TRACK_URL} --apiKey ${DEPENDENCY_TRACK_TOKEN} --projectName ${APP_NAME} --projectVersion latest", + "format": "prettier --write '**/*.{js,ts,tsx}'" + }, + "dependencies": { + "@apollo/client": "^3.6.1", + "@apollo/react-testing": "^4.0.0", + "@react-hook/resize-observer": "^1.2.5", + "axios": "^0.27.2", + "@dco/sdv-ui":"file:./dco-sdv-ui-1.120.0.tgz", + "easy-peasy": "^5.0.4", + "graphql": "^16.4.0", + "jest-fetch-mock": "^3.0.3", + "next": "12.1.0", + "next-http-proxy-middleware": "^1.2.4", + "next-router-mock": "^0.7.4", + "node-fetch": "^3.2.6", + "prom-client": "^14.0.1", + "react": "17.0.2", + "react-charts": "^3.0.0-beta.34", + "react-dom": "17.0.2", + "react-intersection-observer": "^9.2.2", + "swr": "^1.2.2" + }, + "devDependencies": { + "@cyclonedx/bom": "^3.10.1", + "@dependency-track/bom": "^1.0.2", + "@testing-library/jest-dom": "5.16.1", + "@testing-library/react": "12.1.2", + "@testing-library/user-event": "13.5.0", + "@types/react": "17.0.36", + "babel-jest": "^28.0.3", + "cross-env": "^7.0.3", + "eslint": "<8.0.0", + "eslint-config-next": "12.0.4", + "jest": "^28.0.3", + "jest-environment-jsdom": "^28.0.2", + "license-checker": "^25.0.1", + "prettier": "^2.4.1", + "sonarqube-scanner": "^2.8.1", + "typescript": "4.4.2" + }, + "prettier": { + "singleQuote": true, + "jsxSingleQuote": true, + "bracketSpacing": true, + "semi": false, + "printWidth": 120 + } +} diff --git a/developer-console-ui/app/pages/_app.tsx b/developer-console-ui/app/pages/_app.tsx new file mode 100644 index 0000000000000000000000000000000000000000..860b81a28a51d685d8b6a0e557948845e99185e3 --- /dev/null +++ b/developer-console-ui/app/pages/_app.tsx @@ -0,0 +1,23 @@ +import '../styles/globals.css' +import type { AppProps } from 'next/app' +import ForceSession from '../components/auth/ForceSession' +import { ApolloProvider } from '@apollo/client' +import { StoreProvider } from 'easy-peasy' +import { store } from '../services/store.service' +import client from '../libs/apollo' +const App = ({ Component, pageProps: { session, ...pageProps } }: AppProps) => { + return ( + <> + {/* @ts-ignore */} + <StoreProvider store={store}> + <ForceSession> + <ApolloProvider client={client as any}> + {/* @ts-ignore */} + <Component {...pageProps} /> + </ApolloProvider> + </ForceSession> + </StoreProvider> + </> + ) +} +export default App diff --git a/developer-console-ui/app/pages/api/metrics.ts b/developer-console-ui/app/pages/api/metrics.ts new file mode 100644 index 0000000000000000000000000000000000000000..8d599c19571c441de50cf48d2a254158627f4eca --- /dev/null +++ b/developer-console-ui/app/pages/api/metrics.ts @@ -0,0 +1,11 @@ +import { NextApiRequest, NextApiResponse } from 'next' +import { register, collectDefaultMetrics } from 'prom-client' + +collectDefaultMetrics() + +const metrics = async (req: NextApiRequest, res: NextApiResponse) => { + res.setHeader('Content-type', register.contentType) + res.send(await register.metrics()) +} + +export default metrics diff --git a/developer-console-ui/app/pages/dco/addEditScenario/menuForScenario.tsx b/developer-console-ui/app/pages/dco/addEditScenario/menuForScenario.tsx new file mode 100644 index 0000000000000000000000000000000000000000..0443703565b8b901a77b703499f9350cc738dac8 --- /dev/null +++ b/developer-console-ui/app/pages/dco/addEditScenario/menuForScenario.tsx @@ -0,0 +1,48 @@ +import { useMutation } from "@apollo/client"; +import { Button, Icon, Menu } from "@dco/sdv-ui"; +import { useState } from "react"; +import { onClickMenuItem } from "../../../services/functionTrack.service"; +import { DELETE_SCENARIO, onClickScenario, setToastMessageForDeleteScenario } from "../../../services/functionScenario.services"; +import ConditionalAlertBtn from "../../shared/conditionalAlertBtn"; +import NewScenario from "./newScenario"; + +// used for update and delete menu options for scenario +export const MenuForScenario = ({ variant, cellData, setSuccessMsgScenario, setToastOpenScenario }: any) => { + const [isOpen, setIsOpen] = useState(false); + const [target, setTarget]: any = useState(null); + const [isShowDelete, setShowDelete] = useState(false); + const [isShowUpdate, setShowUpdate] = useState(false); + let sid = cellData?.sid + const [deleteScenario] = useMutation(DELETE_SCENARIO, { + onCompleted(data) { + setToastMessageForDeleteScenario(data, setSuccessMsgScenario, setToastOpenScenario, 'success'); + }, + onError(err) { + setToastMessageForDeleteScenario(JSON.parse(JSON.stringify(err)), setSuccessMsgScenario, setToastOpenScenario, 'fail'); + }, + }); + let trackData = [{ text: "Update" }, { text: "Delete" }] + return <> + <Button size="small" style={{ + paddingTop: "0px", + paddingBottom: "0px", + }} ref={setTarget} variant={variant} onClick={(e: any) => { + e.preventDefault(); + setIsOpen(!isOpen); + }} data-testid="btn"> + <Icon name={"context-menu"} /> + </Button> + {/* DON'T REMOVE THIS disabled={trackComp.highestBuild != trackComp.build} DON'T REMOVE THIS */} + <Menu + open={isOpen} + target={target} + items={trackData} + onItemClick={(e: any) => { onClickMenuItem(e?.text == 'Delete' ? setShowDelete : setShowUpdate) }} + onHide={() => { setIsOpen(false); }} + /> + {isShowDelete && <ConditionalAlertBtn show={isShowDelete} onClose={setShowDelete} respectiveId={sid} mutationLoad={deleteScenario} popupMsg={"Are you sure you want to delete this scenario?"} popupState={"Warning!"} respectiveFun={onClickScenario} yes={'Yes'} no={'No'}></ConditionalAlertBtn>} + {isShowUpdate && <NewScenario show={isShowUpdate} onClose={setShowUpdate} path='update' cellData={cellData} setToastOpenScenario={setToastOpenScenario} setSuccessMsgScenario={setSuccessMsgScenario} />} + </> +} +export default MenuForScenario; + diff --git a/developer-console-ui/app/pages/dco/addEditScenario/newScenario.tsx b/developer-console-ui/app/pages/dco/addEditScenario/newScenario.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b84a73cc216fce8a41c59db64a14c2f70061468d --- /dev/null +++ b/developer-console-ui/app/pages/dco/addEditScenario/newScenario.tsx @@ -0,0 +1,117 @@ +import { Button, FileInput, Flex, Headline, Icon, Input, Popup, Select, Spacer, StatusMessage, Title, Toast, Value } from "@dco/sdv-ui"; +import { useRouter } from "next/router"; +import { useState } from "react"; +import BoxToast from "../../../components/layout/boxToast"; +import { getFileSIzeInService, handleNewScenarioSubmitInService, handleUpdateScenarioSubmitInService } from "../../../services/functionScenario.services"; +import { avoidSplChars } from "../../../services/functionShared"; +import { uploadFile } from "../../../services/functionTrack.service"; + +// called on click of new scenario from scenario list page +export function NewScenario({ show, onClose, path, cellData, setToastOpenScenario, setSuccessMsgScenario }: any) { + const [name, setName] = useState<any>(cellData?.scenario); + const [description, setDescription] = useState<any>(cellData?.description); + const [type, setType] = useState<any>(cellData?.type); + const [selectedUploadFile, setUploadFile] = useState(); + const [fileSizeError, setFileSizeError] = useState<boolean>(false); + const [fileNameError, setFileNameError] = useState<boolean>(false); + const maxFileSizeInMB = 1000000; + const minFileSizeInMB = 0; + const typeList = ['MQTT', 'CAN']; + const [isToastOpen, setToastOpen] = useState(false); + let sid = cellData?.sid; + const router = useRouter(); + const [toastMsg, setToastMsg] = useState<string>('') + const [nameError, setNameError] = useState<boolean>(false) + const [typeError, setTypeError] = useState<boolean>(false) + const [fileError, setFileError] = useState<boolean>(false) + + return <> + <Popup invert={true} dim show={show} onClose={onClose} style={{ zIndex: 100 }}> + <Headline data-testid="newComponentPackageHeadline"> + {path == 'create' ? 'New Scenario' : 'Update Scenario'}</Headline> + <Spacer /> + <Flex gutters="default" align="left" justify="space-between"> + <Flex.Item> + <Input labelPosition="floating" label='Name *' type="text" onKeyPress={avoidSplChars} + placeholder="Name *" onValueChange={(x: any) => { setName(x) }} value={name} /> + <Spacer space={1} /> + {nameError && !name && <StatusMessage variant="error"> + Please add a name + </StatusMessage>} + </Flex.Item> + <Flex.Item> + <Select label={"Type *"} labelPosition="floating" placeholder={"Type *"} + value={type} searchable={true} multiple={false} options={typeList} + onChange={(e: string) => { + setType(e) + } + } noResultsMessage="Type not found" + /> + <Spacer space={1} /> + {typeError && !type && <StatusMessage variant="error"> + Please select a type + </StatusMessage>} + </Flex.Item> + </Flex> + <Flex> + <Flex.Item > + <Input labelPosition="floating" label='Description' + placeholder="Description" onValueChange={(x: any) => { + setDescription(x) + }} value={description} /> + </Flex.Item> + </Flex> + <Flex align="right"> + <Flex.Item align="right"> + <Spacer space={2} /> + <Title style={{ 'fontSize': 'large', 'opacity': '.7', 'right': '2em' }}>{path == 'create' ? 'File *' : 'File'}</Title> + <Spacer /> + {path == 'create' ? <FileInput data-testid="uploadFile" accept={".txt, .odx"} onChange={(e: any) => e.target.value ? getFileSIzeInService(e, setUploadFile, uploadFile, maxFileSizeInMB, minFileSizeInMB, setFileSizeError, setFileNameError) : setUploadFile(undefined)} /> + : + <p> + {!selectedUploadFile && <Value><Icon name={"archive"} />{cellData?.filename}</Value>} + <Spacer /> + <FileInput message='Upload a new file' data-testid="uploadFile" accept={".txt, .odx"} onChange={(e: any) => e.target.value ? getFileSIzeInService(e, setUploadFile, uploadFile, maxFileSizeInMB, minFileSizeInMB, setFileSizeError, setFileNameError) : setUploadFile(undefined)} /> + {/* </Box> */} + </p> + } + <Spacer space={1} /> + {fileSizeError ? + <StatusMessage variant="error"> + Please upload file more than {minFileSizeInMB} bytes and upto {maxFileSizeInMB} MB. + </StatusMessage> : <></>} + {fileNameError ? + <><StatusMessage variant="error"> + File name is invalid, remove spaces or + </StatusMessage> + <StatusMessage variant="error" noIcon> + any special character (only accepted charcters - , _ ) + </StatusMessage> + </> + : <></>} + {fileError && !selectedUploadFile && <StatusMessage variant="error"> + Please upload a file + </StatusMessage> + } + </Flex.Item> + </Flex> + <Spacer space={2} /> + <Flex> + <Flex.Item> + {path == 'create' ? <Button width="full" data-testid="btn1" onClick={() => handleNewScenarioSubmitInService({ name, type, description, selectedUploadFile } as const, 'abc@t-systems.com', { setName, setType, setDescription, setUploadFile, setFileSizeError, setFileNameError, setToastMsg, setNameError, setTypeError, setFileError } as const, setToastOpen, onClose)}>Create Scenario</Button> : + <Button width="full" data-testid="btn2" onClick={() => handleUpdateScenarioSubmitInService({ sid, name, type, description, selectedUploadFile } as const, 'abc@t-systems.com', { setName, setType, setDescription, setUploadFile, setFileSizeError, setFileNameError, setSuccessMsgScenario, setNameError, setTypeError, setFileError } as const, setToastOpenScenario, onClose, router)}>Update Scenario</Button>} + </Flex.Item> + </Flex> + <Spacer space={0.5} /> + </Popup> + {/* Toast message for creating new or updationg old scenario */} + {<Toast show={isToastOpen}> + <div> + <BoxToast toastMsg={toastMsg} /> + </div> + </Toast> + } + </> +} + +export default NewScenario; \ No newline at end of file diff --git a/developer-console-ui/app/pages/dco/addSimulation/index.tsx b/developer-console-ui/app/pages/dco/addSimulation/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8a3b039b9dca28f5fccbd9c251ee67fc305c26aa --- /dev/null +++ b/developer-console-ui/app/pages/dco/addSimulation/index.tsx @@ -0,0 +1,215 @@ +import { BackButton, Box, Button, Flex, Headline, Input, NavigationBar, NavigationBarItem, SearchField, Select, Spacer, StatusMessage, Toast } from "@dco/sdv-ui" +import Layout from "../../../components/layout/layout" +import router from "next/router"; +import { useState } from "react"; +import { clearAll, launchSimulation, onLaunchedSimulation } from "../../../services/functionSimulation.service"; +import { useStoreActions, useStoreState } from "easy-peasy"; +import { useMutation, useQuery } from "@apollo/client"; +import { HARDWARE_MODULE, LAUNCH_SIMULATION } from "../../../services/queries"; +import BoxToast from "../../../components/layout/boxToast"; +import TrackList from "../tracksMain/trackList"; +import ScenarioList from "../scenario/scenarioList"; +import { avoidSplChars, invert } from "../../../services/functionShared"; +export function Selectedscenario() { + return useStoreState((state: any) => state.selectedscenario) +} +export function Selectedtrack() { + return useStoreState((state: any) => state.selectedtrack) +} +export function Searchval() { + return useStoreState((state: any) => state.searchval); +} +// click on New simulation and used to launch or discard simulation +const AddSimulation = ({ children }: any) => { + const [title, setTitle] = useState(''); + const [titleError, setTitleError] = useState(false); + const [description, setDescription] = useState(''); + const [environment, setEnvironment] = useState(''); + const [platform, setPlatform] = useState(''); + const [scenarioType, setScenarioType] = useState(''); + const [stypeError, setSTypeError] = useState(false); + const [hardware, setHardware] = useState(''); + const [isToastOpen, setIsToastOpen] = useState(false); + const [toastMsg, setToastMsg] = useState<string>('') + const [activeScenario, setActiveScenario] = useState('active'); + const [activeTrack, setActiveTrack] = useState(''); + const enviroList = ['Development', 'Demo']; + const platformList = ['Task Mangement']; + const scenarioTypeList = ['Over-The-Air Service', + 'Vehicle Management', + 'Data Collection', + 'Remote Control']; + const { data } = useQuery(HARDWARE_MODULE); + let foundTrack = false; + let foundScenario = false; + const scenario = Selectedscenario(); + const track = Selectedtrack(); + const [trackError, setTrackError] = useState(false); + const [scenarioError, setScenarioError] = useState(false); + const [createSimulation] = useMutation(LAUNCH_SIMULATION, { + onCompleted(value) { + onLaunchedSimulation(setSelectedscenario, setSelectedtrack, setIsToastOpen, setToastMsg, value, true) + }, + onError(error) { + onLaunchedSimulation(setSelectedscenario, setSelectedtrack, setIsToastOpen, setToastMsg, error, false) + } + }); + const setSelectedscenario = useStoreActions((actions: any) => actions.setSelectedscenario) + const setSelectedtrack = useStoreActions((actions: any) => actions.setSelectedtrack) + const setSearchval = useStoreActions((actions: any) => actions.setSearchval); + const searchval = Searchval(); + return (<><Layout> + <Box fullHeight padding="none" invert={invert()} variant="body"> + <Flex fullHeight> + <Flex.Item flex={5}> + <Box padding="none" variant="body" fullHeight> + <Flex column fullHeight> + <Flex.Item autoSize> + {/* HEADER */} + <Flex gutters="small" valign="bottom"> + {/* HEADLINE */} + <Flex.Item flex={1}> + <Box padding='sidebar'> + <Flex gutters='large'> + <Flex.Item> + <BackButton data-testid='backButton' + onClick={() => { + clearAll({ setTitle, setDescription, setEnvironment, setPlatform, setSelectedscenario, setSelectedtrack, setScenarioType, setHardware, setSearchval, setTitleError, setSTypeError, setTrackError, setScenarioError } as const); + router.push('/dco/simulation') + }} /> + <Headline level={1}>New Simulation</Headline> + </Flex.Item> + </Flex> + </Box> + </Flex.Item> + {/* NAV */} + <Flex.Item autoSize> + <NavigationBar> + <Box padding="small"> + <NavigationBarItem className={activeScenario} onClick={(x: any) => { setActiveTrack(""); setActiveScenario("active"); setSearchval('') }}>scenario + </NavigationBarItem> + </Box> + <Box padding="small"> + <NavigationBarItem className={activeTrack} onClick={(x: any) => { setActiveTrack("active"); setActiveScenario(""); setSearchval('') }}>track + </NavigationBarItem> + </Box> + </NavigationBar> + </Flex.Item> + {/* RIGHT */} + <Flex.Item flex={1} textAlign='right'> + { + <SearchField value={searchval} onChange={(val: any) => { setSearchval(val.target.value) }} placeholder="search" width="compact" /> + } + </Flex.Item> + </Flex> + </Flex.Item> + {/* MAIN CONTENT */} + <Flex.Item> + <Flex fullHeight> + <Flex.Item> + <Box padding="sidebar" escapePaddingBottom escapePaddingLeft variant='high' transparency="high" fullHeight > + {activeScenario && <ScenarioList path={'sim'}></ScenarioList>} + {activeTrack && <TrackList path={'sim'}></TrackList>} + + {/* scenario screen */} + {scenario.map((x: any) => { if (x.checked) { foundScenario = true; } })} + {!foundScenario && scenarioError && <StatusMessage variant="error"> + Please select at least one scenario + </StatusMessage>} + {/* track screen */} + {track.map((x: any) => { if (x.checked) { foundTrack = true; } })} + {!foundTrack && trackError && <StatusMessage variant="error"> + Please select at least one track + </StatusMessage>} + </Box> + </Flex.Item> + </Flex> + </Flex.Item> + </Flex> + </Box> + </Flex.Item> + <Flex.Item flex={2}> + <Box padding="large" variant="high" fullHeight> + <Headline level={1}>Simulation</Headline> + <Spacer space={1} /> + <Input label="Title *" type="text" onKeyPress={avoidSplChars} labelPosition="floating" value={title} + setValue={setTitle} /> + {(!title && titleError) && <> <Spacer space={1} /><StatusMessage variant="error"> + Please add a title + </StatusMessage></>} + <Spacer space={1} /> + <Input label="Description" labelPosition="floating" value={description} + setValue={setDescription} /> + <Spacer space={1} /> + <Select placeholder="Environment" width="wide" + data-testid="environment" + options={enviroList} + value={environment} + searchable={true} + label='Environment' + labelPosition='floating' + onChange={(e: any) => { setEnvironment(e) }} + ></Select > + <Spacer space={1} /> + <Select placeholder="Platform" width="wide" + data-testid="platform" + options={platformList} + value={platform} + searchable={true} + label='Platform' + labelPosition='floating' + onChange={(e: any) => { setPlatform(e) }} + ></Select > + <Spacer space={1} /> + <Select placeholder="Scenario Type *" width="wide" + data-testid="scenarioType" + options={scenarioTypeList} + value={scenarioType} + searchable={true} + label='Scenario Type' + labelPosition='floating' + onChange={(e: any) => { setScenarioType(e) }} + ></Select > + {(!scenarioType && stypeError) && <> <Spacer space={1} /><StatusMessage variant="error"> + Please select a scenario type + </StatusMessage></>} + <Spacer space={1} /> + <Select placeholder="Hardware" width="wide" + data-testid="hardware" + options={data?.getHardwareModule} + value={hardware} + searchable={true} + label='Hardware' + labelPosition='floating' + onChange={(e: any) => { setHardware(e) }} + ></Select > + <Spacer space={8}></Spacer> + <Flex> + <Flex.Item textAlign="right" colSpan={2}> + <Button onClick={() => { launchSimulation({ title, description, environment, platform, scenario, track, scenarioType, hardware } as const, createSimulation, { setIsToastOpen, setToastMsg, setTitleError, setSTypeError, setTrackError, setScenarioError } as const); setSearchval('') }}> + Launch Simulation + </Button> + </Flex.Item> + <Flex.Item textAlign="center" colSpan={1}> + <Button variant="secondary" onClick={() => { clearAll({ setTitle, setDescription, setEnvironment, setPlatform, setSelectedscenario, setSelectedtrack, setScenarioType, setHardware, setSearchval, setTitleError, setSTypeError, setTrackError, setScenarioError } as const) }}> + Discard + </Button> + </Flex.Item> + + </Flex> + </Box> + </Flex.Item> + </Flex> + </Box> + <Box> + {/* Toast message for launching new simulation */} + {<Toast show={isToastOpen}> + <div> + <BoxToast toastMsg={toastMsg} /> + </div> + </Toast>} + </Box> + </Layout> + </>) +} +export default AddSimulation \ No newline at end of file diff --git a/developer-console-ui/app/pages/dco/addTrack/index.tsx b/developer-console-ui/app/pages/dco/addTrack/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..040da5cebab2dd6b95757f27d3913631f75f78ae --- /dev/null +++ b/developer-console-ui/app/pages/dco/addTrack/index.tsx @@ -0,0 +1,200 @@ +import { BackButton, Box, Button, Flex, FormRow, GridTable, Headline, Input, Spacer, StatusMessage, Toast } from "@dco/sdv-ui"; +import { useRouter } from "next/router"; +import { useEffect, useRef, useState } from "react"; +import Layout from "../../../components/layout/layout"; +import { useLazyQuery, useMutation } from "@apollo/client"; +import { CREATE_TRACK, VEHICLE_LIST } from "../../../services/queries"; +import { mapDataToTable, onCompletedCreateTrack, onselectionchange, onSelectionChanged, saveNewTrack, selectedCheckboxFunction } from "../../../services/functionTrack.service"; +import BoxToast from "../../../components/layout/boxToast"; +import { AgGridReact } from 'ag-grid-react'; +import Status from "../../shared/status"; +import { avoidSplChars, invert, onLoadMore } from "../../../services/functionShared"; + +// on click of New track used to add a track +const AddTrack = (args: any) => { + const router = useRouter(); + const [title, setTitle] = useState(''); + const [titleError, setTitleError] = useState(false); + const [buttonDisable, setButtonDisable] = useState(false); + const [vehiclerror, setVehicleError] = useState(false); + const [isToastOpen, setIsToastOpen] = useState(false); + const [toastMsg, setToastMsg] = useState<string>('') + const [durationError, setDurationError] = useState(false); + const [description, setDescription] = useState(''); + const [duration, setDuration] = useState('7'); + const [datas, setDatas] = useState<any>([]); + const [pageSize, setPageSize] = useState<number>(10); + const [page, setPage] = useState<number>(1); + const gridRef = useRef<AgGridReact>(null); + let [arrVehicle, setArrVehicle] = useState<any>([]); + + const [createTrack] = useMutation(CREATE_TRACK, { + onCompleted(value) { + onCompletedCreateTrack(setButtonDisable, setIsToastOpen, setToastMsg, value, true) + }, + onError(error) { + onCompletedCreateTrack(setButtonDisable, setIsToastOpen, setToastMsg, error, false) + } + }); + let vehicles: { vin: any; country: any; }[] = []; + const [getVehicle, { data }] = useLazyQuery(VEHICLE_LIST, { + variables: { search: null, query: null, page: page, size: pageSize, sort: null }, + }); + const [onSelectedRows, setonSelectedRows] = useState<any>([]); + useEffect(() => { + getVehicle() + }, []) + useEffect(() => { + mapDataToTable(data, setDatas, datas) + }, [data]) + const columns = [ + { + checkboxSelection: true, + headerName: 'VIN', + field: 'vin', + }, + { + headerName: 'Type', + field: 'type', + }, + { + headerName: 'Last connected', + field: 'lastconnected', + }, + { + headerName: 'Country', + field: 'country', + }, + { + headerName: 'Brand', + field: 'brand' + }, + { + headerName: 'Model', + field: 'model' + }, + { + headerName: 'Staus', + field: 'status', + } + ] + useEffect(() => { + selectedCheckboxFunction(onSelectedRows, gridRef) + }, [datas]); + function checkLoadmore() { + if (data?.vehicleReadByQuery.pages != page) { + return 'Load more'; + } + return data?.vehicleReadByQuery.pages == page ? 'No more data' : 'Loading'; + } + return (<Layout> + <Box fullHeight padding="none" invert={invert()} variant="body"> + <Flex fullHeight> + <Flex.Item flex={5}> + <Box padding="none" variant="body" fullHeight> + <Flex column fullHeight> + <Flex.Item autoSize> + <Flex column> + <Flex.Item > + {/* HEADER */} + <Flex gutters="small" valign="bottom"> + {/* HEADLINE */} + <Flex.Item flex={1}> + <Box padding="sidebar"> + <Flex gutters="large"> + <Flex.Item> + <BackButton data-testid="backButtondata" onClick={() => router.back()} /> + <Headline level={1}>New Track</Headline> + </Flex.Item> + </Flex> + </Box> + </Flex.Item> + </Flex> + </Flex.Item> + </Flex> + </Flex.Item> + {/* MAIN CONTENT */} + <Flex.Item> + <Flex fullHeight> + <Flex.Item> + <Box padding="sidebar" fullHeight escapePaddingBottom variant='high' transparency="high" > + <Flex gutters="large"> + <Flex.Item autoSize> + <Headline level={3}>{datas?.length} Potential vehicles</Headline> + </Flex.Item> + <Flex.Item> + <Headline level={3}>{arrVehicle?.length} Selected</Headline> + </Flex.Item> + </Flex> + <Flex> + <Flex.Item> + <Box escapePaddingLeft escapePaddingRight escapePaddingBottom> + {(arrVehicle?.length == 0 && vehiclerror) && <StatusMessage variant="error"> + Please select at least one vehicle + </StatusMessage>} + <GridTable rowSelection={"multiple"} rowMultiSelectWithClick={true} + cellRendererPostProcessor={(preRendered: any, cell: any) => { + return <>{cell?.colDef.field === 'status' && <Status status={cell.data.status} type={'VD'}></Status> || preRendered} + </> + }} + {...args} ref={gridRef} columnDefs={columns} rowData={datas} + onSelectionChanged={(e) => { onselectionchange(e.api.getSelectedNodes(), setArrVehicle, vehicles, onSelectionChanged, gridRef, setonSelectedRows) }} + ></ GridTable> + {datas?.length > 10 && ( + <Box align='center' padding='small'> + <Button onClick={() => onLoadMore(setPageSize, setPage, getVehicle, pageSize, page)} disabled={data?.vehicleReadByQuery.pages == page || data == undefined} size='small' variant='naked'> + {checkLoadmore()} + </Button> + </Box> + )} + </Box> + </Flex.Item> + </Flex> + </Box> + </Flex.Item> + </Flex> + </Flex.Item> + </Flex> + </Box> + </Flex.Item> + <Flex.Item flex={2}> + <Box padding="large" variant="high" fullHeight> + <Headline level={1}>Track Config</Headline> + <Spacer space={1} /> + <FormRow> + <Input label="Track Title *" type="text" onKeyPress={avoidSplChars} labelPosition="floating" value={title} + setValue={setTitle} /> + </FormRow> + <Spacer space={1} /> + {(!title && titleError) && <StatusMessage variant="error"> + Please add a title + </StatusMessage>} + <FormRow> + <Input label="Description" labelPosition="floating" value={description} + setValue={setDescription} /> + </FormRow> + <Spacer space={1} /> + <FormRow> + <Input label="Duration *" labelPosition="floating" value={duration} type="number" + setValue={setDuration} accessoryText="days" withAccessory /> + </FormRow> + <Spacer space="05" /> + {(!duration && durationError) && <StatusMessage variant="error"> + Please define a duration + </StatusMessage>} + <Spacer space={10}></Spacer> + <Button width="wide" data-testid="saveButton" onClick={() => saveNewTrack(title, arrVehicle, duration, description, createTrack, { setIsToastOpen, setToastMsg, setTitleError, setDurationError, setVehicleError, setButtonDisable } as const)} disabled={buttonDisable}>Save Track</Button> + </Box> + </Flex.Item> + </Flex> + </Box> + {/* Toast message for creating new track */} + {<Toast show={isToastOpen}> + <div> + <BoxToast toastMsg={toastMsg} /> + </div> + </Toast>} + </Layout > + ) +} +export default AddTrack; diff --git a/developer-console-ui/app/pages/dco/index.tsx b/developer-console-ui/app/pages/dco/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..528fe484137fbb890dddcc4c1fe4db2114a84332 --- /dev/null +++ b/developer-console-ui/app/pages/dco/index.tsx @@ -0,0 +1,116 @@ +import { Flex, Box, Headline, Button, NavigationBar, NavigationBarItem, StatusMessage, Spacer } from '@dco/sdv-ui' +import { useStoreActions, useStoreState } from 'easy-peasy' +import { useRouter } from 'next/router' +import { useState } from 'react' +import ActiveLink from '../../components/layout/ActiveLink' +import Layout from '../../components/layout/layout' +import { checkRoute, invert } from '../../services/functionShared' +import { onClickNewSimulation } from '../../services/functionSimulation.service' +import { onClickNewTrack } from '../../services/functionTrack.service' +import NewScenario from './addEditScenario/newScenario' +export function Count() { + return useStoreState((state: any) => state.count) +} +// main landing page +const Dco = ({ children }: any) => { + const router = useRouter() + const [showScenarioPopup, setShowScenarioPopup] = useState(false) + const setCompid = useStoreActions((actions: any) => actions.setCompid) + const pathname = router?.pathname + const trackTabClicked = router.pathname === '/dco/tracksMain' + const libTabClicked = router.pathname.includes('/dco/scenario') + const simulationTabClicked = router.pathname === '/dco/simulation' + const token = localStorage.getItem('token') + return (<>{ + <Layout> + <Box padding='none' invert={invert()} variant='body' fullHeight> + <Flex column fullHeight> + {token ? <> <Flex.Item autoSize> + {/* Header */} + <Flex valign='bottom'> + {/* HEADLINE */} + <Flex.Item flex={1} textAlign='right'> + <Box padding='sidebar'> + <Flex gutters='small'> + <Flex.Item autoSize> + {libTabClicked && <Headline level={1}> {Count() || 0} scenarios</Headline>} + {trackTabClicked && <Headline level={1}>{Count() || 0} tracks</Headline>} + {simulationTabClicked && <Headline level={1}> {Count() || 0} simulations</Headline>} + </Flex.Item> + <Flex.Item textAlign='left' valign='bottom'> + {libTabClicked && (<Button style={{ marginTop: '-.3em' }} data-testid='newReleaseBtn' + onClick={() => { setShowScenarioPopup(true) }}> + New Scenario + </Button>)} + {trackTabClicked && (<Button style={{ marginTop: '-.3em' }} data-testid='addTrackbtn' + onClick={() => onClickNewTrack(setCompid)}> + New Track + </Button>)} + {simulationTabClicked && (<Button style={{ marginTop: '-.3em' }} data-testid='addSimulationBtn' + onClick={() => onClickNewSimulation()}> + New Simulation + </Button>)} + </Flex.Item> + </Flex> + </Box> + </Flex.Item> + <NewScenario show={showScenarioPopup} onClose={setShowScenarioPopup} path='create' /> + {/* NAV */} + <Flex.Item autoSize> + <NavigationBar> + <ActiveLink href={checkRoute('/dco/scenario', router, pathname)}> + <NavigationBarItem>scenario</NavigationBarItem> + </ActiveLink> + <ActiveLink href={checkRoute('/dco/tracksMain', router, pathname)}> + <NavigationBarItem>tracks</NavigationBarItem> + </ActiveLink> + <ActiveLink href={checkRoute('/dco/simulation', router, pathname)}> + <NavigationBarItem>simulations</NavigationBarItem> + </ActiveLink> + </NavigationBar> + </Flex.Item> + {/* RIGHT */} + <Flex.Item flex={1} textAlign='right'> + </Flex.Item> + </Flex> + </Flex.Item> + <Flex.Item> + <Flex fullHeight> + <Flex.Item> + <Box fullHeight variant='high'>{children}</Box> + </Flex.Item> + </Flex> + </Flex.Item> + </> + : + <> + <Flex.Item align="center" autoSize > + <Flex> + <Flex.Item> + <Spacer space={10}></Spacer> + <Spacer space={5}></Spacer> + <Box interactive variant='high' padding='large' elevation='low'> + <Flex rows={1} gutters="small"> + <Flex.Item valign='center'> <StatusMessage noIcon variant='secondary'>Session has been expired click + </StatusMessage></Flex.Item> + <Flex.Item > <Button variant='primary' size='small' align='right' onClick={() => { router.replace('/login') }}> here </Button> + </Flex.Item> + <Flex.Item valign='center'> <StatusMessage noIcon variant='secondary' >to login again + </StatusMessage></Flex.Item> + </Flex> + + + </Box> + </Flex.Item> + </Flex> + </Flex.Item> + </>} + </Flex> + </Box> + </Layout> + + } + + </>) +} +export default Dco diff --git a/developer-console-ui/app/pages/dco/scenario/index.tsx b/developer-console-ui/app/pages/dco/scenario/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..85fdb8001c55d1217cca30bfeac4a0eeff7892c1 --- /dev/null +++ b/developer-console-ui/app/pages/dco/scenario/index.tsx @@ -0,0 +1,14 @@ +import { Box } from '@dco/sdv-ui' +import Dco from '..' +import ScenarioList from './scenarioList' + +// scenario listing table +const Scenario = (args: any) => { + return (<Dco> + <Box fullHeight scrollY> + <ScenarioList></ScenarioList> + </Box> + </Dco>) +} + +export default Scenario diff --git a/developer-console-ui/app/pages/dco/scenario/scenarioList.tsx b/developer-console-ui/app/pages/dco/scenario/scenarioList.tsx new file mode 100644 index 0000000000000000000000000000000000000000..01231b542a8ac9224aa57fe34ee15da3ac771f37 --- /dev/null +++ b/developer-console-ui/app/pages/dco/scenario/scenarioList.tsx @@ -0,0 +1,123 @@ +import { Box, Check, Table, Toast } from "@dco/sdv-ui" +import { useStoreActions, useStoreState } from "easy-peasy" +import { useEffect, useState } from "react" +import MenuForScenario from "../addEditScenario/menuForScenario" +import Pagination from "../../shared/paginationTable" +import { getLibData, libRowData } from "../../../services/functionScenario.services" +import BoxToast from "../../../components/layout/boxToast" +export function Selectedscenario() { + return useStoreState((state: any) => state.selectedscenario) +} +export function Searchval() { + return useStoreState((state: any) => state.searchval); +} +const ScenarioList = ({ path }: any) => { + const setSelectedscenario = useStoreActions((actions: any) => actions.setSelectedscenario) + const theArray = Selectedscenario(); + const setCount = useStoreActions((actions: any) => actions.setCount) + const [isToastOpenScenario, setToastOpenScenario] = useState(false) + const [successMsgScenario, setSuccessMsgScenario] = useState('') + const [currentPage, setCurrentPage] = useState(1) + const searchval = Searchval(); + const [pageData, setPageData] = useState({ + rowData: [], + isLoading: false, + totalPages: 0, + totalScenarios: 0, + }) + + useEffect(() => { + setPageData((prevState) => ({ + ...prevState, + rowData: [], + isLoading: true, + })) + getLibData(currentPage, searchval).then((info) => { + setPageData({ + isLoading: false, + rowData: libRowData(info), + totalPages: info?.data?.searchScenarioByPattern?.pages, + totalScenarios: info?.data?.searchScenarioByPattern?.total, + }) + setCount(info?.data.searchScenarioByPattern?.total); + }) + }, [currentPage, searchval]) + const columns = [ + { + Header: '', + accessor: 'check', + hidden: path == 'sim' ? false : true, + formatter: (value: any, cell: any) => { + return (<Check + checked={theArray?.find((e: any) => { + if (e.id == cell.row.values.sid && e.checked) { + return true + } else { return false } + })} + onChange={(e: any, val: any) => { + theArray.map((v: any) => { + if (v?.id == cell.row.values.sid) { + v.checked = e.target.checked + } else { + theArray.push({ id: cell.row.values.sid, checked: e.target.checked }) + } + }) + let newArr = [...new Map(theArray.map((s: any) => [s['id'], s])).values()] + setSelectedscenario(newArr) + }} + />) + } + }, { + Header: 'ID', + accessor: 'sid', + }, + { + Header: 'Scenario', + accessor: 'scenario', + }, + { + Header: 'Type', + accessor: 'type', + }, + { + Header: 'Filename', + accessor: 'filename', + }, + { + Header: 'Last Updated', + accessor: 'lastUpdated', + }, + { + Header: '', + accessor: 'menu', + hidden: path == 'sim' ? true : false, + formatter: (value: any, cell: any) => { + return (<MenuForScenario variant='naked' cellData={cell?.row.values} setToastOpenScenario={setToastOpenScenario} setSuccessMsgScenario={setSuccessMsgScenario}></MenuForScenario>) + } + }, + ] + return (<> + {/* @ts-ignore */} + <Table data-testid="table" columns={columns} + data={pageData.rowData} initialSortBy={[ + { + id: 'lastUpdated', + desc: true + } + ]} + noDataMessage='No Rows To Show' + /> + <Box align='right' padding='small'> + <Pagination totalRows={pageData.totalScenarios} pageChangeHandler={setCurrentPage} rowsPerPage={10} /> + </Box> + {/* Toast message for deleting a scenario */} + {<Toast show={isToastOpenScenario}> + <div> + <BoxToast toastMsg={successMsgScenario} /> + </div> + </Toast> + } + </> + ) +} +export default ScenarioList \ No newline at end of file diff --git a/developer-console-ui/app/pages/dco/simulation/index.tsx b/developer-console-ui/app/pages/dco/simulation/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f829001c12ac042d7590e3a4c741f6b34b3e6dd0 --- /dev/null +++ b/developer-console-ui/app/pages/dco/simulation/index.tsx @@ -0,0 +1,85 @@ +import { Box, Table } from '@dco/sdv-ui' +import { useStoreActions } from 'easy-peasy' +import { useEffect, useState } from 'react' +import Dco from '..' +import { getSimData, simRowData } from '../../../services/functionSimulation.service' +import CounterWithToolTip from '../../shared/counterWithToolTip' +import Pagination from '../../shared/paginationTable' +import Status from '../../shared/status' + +// simulation table +const Simulation = () => { + const setCount = useStoreActions((actions: any) => actions.setCount) + const [currentPage, setCurrentPage] = useState(1) + const [pageData, setPageData] = useState({ + rowData: [], + isLoading: false, + totalPages: 0, + totalSimulations: 0, + }) + useEffect(() => { + setPageData((prevState) => ({ + ...prevState, + rowData: [], + isLoading: true, + })) + getSimData(currentPage).then((info) => { + setPageData({ + isLoading: false, + rowData: simRowData(info) as any, + totalPages: info?.data?.simulationReadByQuery?.pages, + totalSimulations: info?.data?.simulationReadByQuery?.total, + }) + setCount(info?.data.simulationReadByQuery?.total); + }) + }, [currentPage]) + + const columns = [{ + Header: 'Simulation Name', + accessor: 'name', + }, { + Header: 'Status', + accessor: 'status', + formatter: (value: any, cell: any) => { + return <Status status={cell?.row?.values?.status} type={'SS'}></Status> + } + }, { + Header: 'Number of vehicles', + accessor: 'numberVehicles', + }, { + Header: 'Brand', + accessor: 'brand', + formatter: (value: any, cell: any) => { + return <CounterWithToolTip toolTipVal={[...new Set(cell?.row?.values?.brand)]}></CounterWithToolTip> + }, + + }, { + Header: 'Scenario Type', + accessor: 'type', + }, { + Header: 'Number of Scenarios', + accessor: 'numberScenarios', + }, { + Header: 'Platform', + accessor: 'platform', + }, { + Header: 'Environment', + accessor: 'env', + }, + { + Header: 'Start Date ', + accessor: 'date', + }] + + return (<Dco> + {/* @ts-ignore */} + <Table columns={columns} + data={pageData.rowData} + /> + <Box align='right' padding='small'> + <Pagination totalRows={pageData.totalSimulations} pageChangeHandler={setCurrentPage} rowsPerPage={10} /> + </Box> + </Dco> + ) +} +export default Simulation diff --git a/developer-console-ui/app/pages/dco/tracksMain/index.tsx b/developer-console-ui/app/pages/dco/tracksMain/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..eaa482e78f6e3bfc861e9eea6209514535c85f3e --- /dev/null +++ b/developer-console-ui/app/pages/dco/tracksMain/index.tsx @@ -0,0 +1,17 @@ +import { Box } from '@dco/sdv-ui' +import Dco from '..' +import TrackList from './trackList' + +// used in tracklisting page and new simulation +const TracksMain = () => { + return ( + <> + <Dco> + <Box fullHeight padding='none' scrollY> + <TrackList></TrackList> + </Box> + </Dco> + </> + ) +} +export default TracksMain diff --git a/developer-console-ui/app/pages/dco/tracksMain/trackInfo/index.tsx b/developer-console-ui/app/pages/dco/tracksMain/trackInfo/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b48747545106eda01382a72fe66eceb48fccbfa2 --- /dev/null +++ b/developer-console-ui/app/pages/dco/tracksMain/trackInfo/index.tsx @@ -0,0 +1,68 @@ +import { BackButton, Box, Flex, Headline, NavigationBar } from '@dco/sdv-ui' +import { useStoreState } from 'easy-peasy' +import router from 'next/router' +import Layout from '../../../../components/layout/layout' +import {invert } from '../../../../services/functionShared' +import TrackDetails from './trackDetails' +export function TrackName() { + return useStoreState((state: any) => state.tname) +} +// tracks info page +const TrackInfo = ({ children }: any) => { + return ( + <Layout> + <Box padding='none' invert={invert()} variant='body' fullHeight> + <Flex column fullHeight> + <Flex.Item autoSize> + {/* HEADER */} + <Flex valign='bottom'> + {/* HEADLINE */} + <Flex.Item flex={1}> + <Box padding='sidebar'> + <Flex gutters='large'> + <Flex.Item autoSize> + <BackButton data-testid='backButton' onClick={() => router.push('/dco/tracksMain')} /> + <Headline level={1}>{TrackName()}</Headline> + </Flex.Item> + </Flex> + </Box> + </Flex.Item> + {/* NAV */} + <Flex.Item autoSize> + <NavigationBar> + </NavigationBar> + </Flex.Item> + {/* RIGHT */} + <Flex.Item flex={1} textAlign='right'></Flex.Item> + </Flex> + </Flex.Item> + {/* MAIN CONTENT */} + <Flex.Item> + <Flex fullHeight> + <Flex.Item flex={2}> + <Flex fullHeight> + <Flex.Item> + <Box variant='high' transparency='medium' fullHeight scrollVertical> + <TrackDetails /> + </Box> + </Flex.Item> + </Flex> + </Flex.Item> + <Flex.Item flex={6}> + <Flex fullHeight> + <Flex.Item> + <Box variant='high' fullHeight scrollVertical> + {children} + </Box> + </Flex.Item> + </Flex> + </Flex.Item> + </Flex> + </Flex.Item> + </Flex> + </Box> + </Layout> + ) +} + +export default TrackInfo diff --git a/developer-console-ui/app/pages/dco/tracksMain/trackInfo/trackDetails/index.tsx b/developer-console-ui/app/pages/dco/tracksMain/trackInfo/trackDetails/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e8e12169bd8871173f957fefcc96bb9ab6fae236 --- /dev/null +++ b/developer-console-ui/app/pages/dco/tracksMain/trackInfo/trackDetails/index.tsx @@ -0,0 +1,88 @@ +import { useQuery } from "@apollo/client"; +import { Box, Flex, Headline, Spacer, Value, ValueGroup } from "@dco/sdv-ui" +import { useStoreState } from "easy-peasy"; +import { getCompNVersion, getDevices, getVehicleList } from "../../../../../services/functionTrack.service"; +import { TRACK_DETAILS } from "../../../../../services/queries"; +import { getValArray } from "../../../../../services/functionShared"; +import TagLists from "../../../../shared/tagLists"; +export function TrackId() { + return useStoreState((state: any) => state.tid); +} +// displays list of track details page, on click of track from listing page->left page +const TrackDetails = () => { + const { data } = useQuery(TRACK_DETAILS, { + variables: { id: TrackId() }, + }); + let arrCountry: any[] = []; + let arrBrand: any[] = []; + let arr: any[] = []; + let arrComponent: any[] = []; + let arrCompnVersionVal: any[] = []; + let arrDevices: any[] = []; + const datasVehicle = getVehicleList(data); + arrCountry = getValArray(datasVehicle, 'country'); + arrBrand = getValArray(datasVehicle, 'brand'); + arr = getValArray(datasVehicle, 'devices'); + arrDevices=getCompNVersion(arr, arrDevices, arrComponent) + arrCompnVersionVal=getDevices(arrComponent, arrCompnVersionVal) + return ( + <Box padding="sidebar"> + <Flex> + <Flex.Item flex={2}> + <Headline level={2}>Details</Headline> + <Spacer space={2} /> + <ValueGroup title="Region"> + <Value> + <Spacer space={0.3} /> + <TagLists data={arrCountry} type={'countries'} from={"trackDetails"} /> + </Value> + </ValueGroup> + <Spacer space={0.2} /> + <ValueGroup title="Brand"> + <Value> + <Spacer space={0.3} /> + <TagLists data={arrBrand} type={'brands'} from={"trackDetails"} /> + </Value> + </ValueGroup> + <Spacer space={0.2} /> + <ValueGroup title="Software component, Version"> + <Value> + <Spacer space={0.3} /> + <TagLists data={arrCompnVersionVal} type={'compNVersion'} from={"trackDetails"} /> + </Value> + </ValueGroup> + <Spacer space={0.2} /> + <ValueGroup title="Devices"> + <Value> + <Spacer space={0.3} /> + <TagLists data={arrDevices} type={'devices'} from={"trackDetails"} /> + </Value> + </ValueGroup> + <Spacer space={0.2} /> + <ValueGroup title="Name"> + <Value> + {data?.findTrackById.name} + </Value> + </ValueGroup> + <ValueGroup title="Type"> + <Value> + {data?.findTrackById.trackType} + </Value> + </ValueGroup> + <ValueGroup title="Description"> + <Value> + {data?.findTrackById.description} + </Value> + </ValueGroup> + <ValueGroup title="Duration"> + <Value> + {data?.findTrackById.duration} {data && 'days'} + </Value> + </ValueGroup> + </Flex.Item> + </Flex> + </Box> + ) +} + +export default TrackDetails \ No newline at end of file diff --git a/developer-console-ui/app/pages/dco/tracksMain/trackInfo/trackVehicleDetails/index.tsx b/developer-console-ui/app/pages/dco/tracksMain/trackInfo/trackVehicleDetails/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..09096e834934ab4e88a17e36ec655c6c99568e50 --- /dev/null +++ b/developer-console-ui/app/pages/dco/tracksMain/trackInfo/trackVehicleDetails/index.tsx @@ -0,0 +1,67 @@ +import { useQuery } from '@apollo/client' +import { Box, Flex, Headline, Spacer, Table } from '@dco/sdv-ui' +import { useStoreState } from 'easy-peasy' +import TrackInfo from '..' +import { getVehicleList, } from '../../../../../services/functionTrack.service' +import { TRACK_DETAILS } from '../../../../../services/queries' +import Status from '../../../../shared/status' + +export function TrackId() { + return useStoreState((state: any) => state.tid) +} +// displays list of vehicles +const TrackVehicleDetails = (args: any) => { + const { data } = useQuery(TRACK_DETAILS, { + variables: { id: TrackId() }, + }) + const columns = [ + { + Header: 'VIN', + accessor: 'vin', + }, + { + Header: 'Type', + accessor: 'type', + }, + { + Header: 'Last connected', + accessor: 'lastconnected', + }, + { + Header: 'Country', + accessor: 'country', + }, + { + Header: 'Brand', + accessor: 'brand', + }, + { + Header: 'Staus', + accessor: 'status', + formatter: (value: any, cell: any) => { + return <Status status={cell?.row.values.status} type={'VD'}></Status> + }, + }, + ] + const datas = getVehicleList(data) + return ( + <TrackInfo> + <Flex column> + <Flex.Item> + <Box paddingLeft='small' escapePaddingBottom paddingTop='large'> + <Headline level={2}> + {datas?.length} {datas?.length == 1 ? 'vehicle' : 'vehicles'} + </Headline> + <Spacer /> + </Box> + <Box> + {/* @ts-ignore */} + <Table columns={columns} data={datas} noDataMessage='No rows to show'></Table> + </Box> + </Flex.Item> + </Flex> + </TrackInfo> + ) +} + +export default TrackVehicleDetails diff --git a/developer-console-ui/app/pages/dco/tracksMain/trackList.tsx b/developer-console-ui/app/pages/dco/tracksMain/trackList.tsx new file mode 100644 index 0000000000000000000000000000000000000000..631fa02ddb85e57e26b175befe88527b6bc2377d --- /dev/null +++ b/developer-console-ui/app/pages/dco/tracksMain/trackList.tsx @@ -0,0 +1,148 @@ +import { Box, Check, Icon, Table, Toast } from '@dco/sdv-ui' +import { useStoreActions } from 'easy-peasy' +import { useEffect, useState } from 'react' +import { deleteTrackFun, getTrackData, onClickDeleteTrack, onClickTrackColumn, Searchval, Selectedtrack, setToastMessageForDeleteTrack, trackRowData } from '../../../services/functionTrack.service' +import CounterWithToolTip from '../../shared/counterWithToolTip' +import ConditionalAlertBtn from '../../shared/conditionalAlertBtn' +import { useMutation } from '@apollo/client' +import { DELETE_TRACK } from '../../../services/queries' +import BoxToast from '../../../components/layout/boxToast' +import { Pagination } from '../../shared/paginationTable' + +// track table on listing page +const TrackList = ({ path }: any) => { + const setSelectedtrack = useStoreActions((actions: any) => actions.setSelectedtrack) + const theArray = Selectedtrack(); + const setTid = useStoreActions((actions: any) => actions.setTid) + const setTname = useStoreActions((actions: any) => actions.setTname) + const setCount = useStoreActions((actions: any) => actions.setCount) + const [isShowAlert, setShowAlert] = useState(false); + const [deleteTrackId, setdeleteTrackId] = useState(''); + const [isToastOpen, setToastOpen] = useState(false); + const [toastMsg, setToastMsg] = useState<string>('') + const [currentPage, setCurrentPage] = useState(1) + const searchval = Searchval() + const [deleteTrack] = useMutation(DELETE_TRACK, { + onCompleted(data2) { + setToastMessageForDeleteTrack(data2, setToastMsg, setToastOpen, 'success'); + }, + onError(err) { + setToastMessageForDeleteTrack(JSON.parse(JSON.stringify(err)), setToastMsg, setToastOpen, 'fail'); + } + }); + const columns = [ + { + Header: '', + accessor: 'check', + hidden: path == 'sim' ? false : true, + formatter: (value: any, cell: any) => { + return (<Check + checked={theArray?.find((e: any) => { + if (e.id == cell.row.values.trackID && e.checked) { + return true + } else { return false } + })} + onChange={(e: any, val: any) => { + theArray.map((v: any) => { + if (v?.id == cell.row.values.trackID) { + v.checked = e.target.checked + } else { + theArray.push({ id: cell.row.values.trackID, checked: e.target.checked }) + } + }) + let newArr = [...new Map(theArray.map((s: any) => [s['id'], s])).values()] + setSelectedtrack(newArr) + }} + />) + } + }, + { + Header: 'Track ID', + accessor: 'trackID', + valign: 'top', + }, + { + Header: 'Track Name', + accessor: 'trackName', + hidden: path == 'sim' ? true : false, + onClick: (e: any, index: any) => { + onClickTrackColumn(index, setTname, setTid) + }, + }, + { + Header: 'Track Name', + accessor: 'trackNameSim', + hidden: path == 'sim' ? false : true, + + }, + { + Header: 'Track Status', + accessor: 'trackStatus', + }, + { + Header: 'Track Type', + accessor: 'trackType', + }, + { + Header: 'Number of vehicles', + accessor: 'numberofvehicles', + }, + { + Header: 'Country', + accessor: 'country', + formatter: (value: any, cell: any) => { + return <CounterWithToolTip toolTipVal={cell?.row?.values?.country}></CounterWithToolTip> + }, + }, + { + Header: ' ', + accessor: 'delete', + hidden: path == 'sim' ? true : false, + formatter: (value: any, cell: any) => { + return <Icon key={value} style={{ cursor: 'pointer' }} name='delete' data-testid='delete-btn' onClick={() => { deleteTrackFun(setShowAlert, setdeleteTrackId, isShowAlert, cell.row.values.trackID) }} /> + } + }, + ] + const [pageData, setPageData] = useState({ + rowData: [], + isLoading: false, + totalPages: 0, + totalTracks: 0, + }) + + useEffect(() => { + setPageData((prevState) => ({ + ...prevState, + rowData: [], + isLoading: true, + })) + getTrackData(currentPage, searchval).then((info) => { + setPageData({ + isLoading: false, + rowData: trackRowData(info) as any, + totalPages: info?.data?.searchTrackByPattern?.pages, + totalTracks: info?.data?.searchTrackByPattern?.total, + }) + setCount(info?.data?.searchTrackByPattern?.total); + }) + }, [currentPage, searchval]) + return ( + <> + {/* @ts-ignore */} + <Table columns={columns} data={pageData.rowData} + /> + <ConditionalAlertBtn show={isShowAlert} onClose={setShowAlert} respectiveId={deleteTrackId} mutationLoad={deleteTrack} popupMsg={'Are you sure you want to delete this track?'} popupState={'Warning!'} respectiveFun={onClickDeleteTrack} yes={'Yes'} no={'No'}></ConditionalAlertBtn> + <Box align='right' padding='small'> + <Pagination totalRows={pageData.totalTracks} pageChangeHandler={setCurrentPage} rowsPerPage={10} /> + </Box> + {/* Toast message for deleting a track */} + {<Toast show={isToastOpen}> + <div> + <BoxToast toastMsg={toastMsg} /> + </div> + </Toast>} + </> + ) +} + +export default TrackList diff --git a/developer-console-ui/app/pages/error/index.tsx b/developer-console-ui/app/pages/error/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..5faa298776eaae967c1d1ecadfc37f71444926fc --- /dev/null +++ b/developer-console-ui/app/pages/error/index.tsx @@ -0,0 +1,82 @@ +import { Box, Button, Flex, Headline, Icon, Input, Spacer, StatusMessage, Value } from "@dco/sdv-ui" +import { useStoreActions, useStoreState } from "easy-peasy" +import { useRouter } from "next/router"; +import { useRef } from "react"; +import Layout from "../../components/layout/layout"; +import { onSubmit } from "../../services/credentials.service"; +export function Error() { + const invert = useStoreState((state: any) => state.invert) + const userName = useRef(""); + const setUser = useStoreActions((actions: any) => actions.setUser) + const router = useRouter(); + const pass = useRef(""); + return ( + <Layout> + <Box fullHeight invert={invert} variant='high' padding="large"> + <Spacer space={5} /> + <Box padding="large" > + <Flex justify="center"> + <Flex.Item> + </Flex.Item> + <Flex.Item justify-content="center" textAlign="center" align="center" > + <Box variant="body" padding="large" > + <Headline level={2}>Welcome to SDV Developer Console <Icon name='tbbui'></Icon></Headline> + <Value>Please enter your credentials for best experience</Value> + {/* <Headline level={4}>Please enter your username and password</Headline> */} + </Box> + <Box variant="body" transparency="high" padding="large" > + <StatusMessage variant="error"> + Please enter a valid username and password + </StatusMessage> + <Flex column> + <Flex.Item> + <Input withAccessory accessoryIcon="user" label="Username" onValueChange={(e) => (userName.current = e) + }></Input> + </Flex.Item> + <Spacer /> + <Flex.Item> + <Input withAccessory accessoryIcon="lock" type="password" label="Password" onValueChange={(e) => (pass.current = e) + }></Input> + </Flex.Item> + <Spacer space={4} /> + <Flex.Item align="left"> + {/* <Label text="Remember you?"> + <Check + name="demo" + value="1" + /> + </Label> */} + </Flex.Item> + <Spacer space={1} /> + <Flex.Item textAlign="center"> + <Button data-testId="submitBtn" width="full" onClick={() => { + onSubmit(userName, pass, setUser, router) + .then(result => { + if (result.data.searchScenarioByPattern) { + setUser(true) + router.replace('/dco/scenario') + } + else { + router.replace('/error') + } + }) + .then((res) => res) + .catch(error => { router.replace('/error') }) + }}>Login</Button> + </Flex.Item> + </Flex> + </Box> + </Flex.Item> + <Flex.Item> + </Flex.Item> + </Flex> + </Box> + </Box > + </Layout> + + ) +} +export default Error + + + diff --git a/developer-console-ui/app/pages/index.tsx b/developer-console-ui/app/pages/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..38dfb0bf0523252ebc32d7d86c195a3ff05fcc56 --- /dev/null +++ b/developer-console-ui/app/pages/index.tsx @@ -0,0 +1,10 @@ +import '@dco/sdv-ui/dist/assets/main.css' +import Layout from '../components/layout/layout'; + +const Home = () => ( +<> +<Layout></Layout> +</> +); + +export default Home diff --git a/developer-console-ui/app/pages/login/index.tsx b/developer-console-ui/app/pages/login/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e8be46b366b5311ce8501724665eed0ca6561a57 --- /dev/null +++ b/developer-console-ui/app/pages/login/index.tsx @@ -0,0 +1,78 @@ +import { Box, Button, Flex, Headline, Icon, Input, Spacer, Value } from "@dco/sdv-ui" +import { useStoreActions, useStoreState } from "easy-peasy" +import { useRouter } from "next/router"; +import { useRef } from "react"; +import Layout from "../../components/layout/layout"; +import { Link } from "../../libs/apollo"; +import { onSubmit } from "../../services/credentials.service"; +export function Login() { + localStorage.removeItem('token'); + const invert = useStoreState((state: any) => state.invert) + const setUser = useStoreActions((actions: any) => actions.setUser) + const userName = useRef(""); + const router = useRouter(); + const pass = useRef(""); + return ( + <Layout> + <Box fullHeight invert={invert} variant='high' padding="large"> + <Spacer space={5} /> + <Box padding="large" > + <Flex justify="center"> + <Flex.Item> + </Flex.Item> + <Flex.Item justify-content="center" textAlign="center" align="center" > + <Box variant="body" padding="large" > + <Headline level={2}>Welcome to SDV Developer Console <Icon name='tbbui'></Icon></Headline> + <Value>Please enter your credentials for best experience</Value> + </Box> + <Box variant="body" transparency="high" padding="large" > + <Flex column> + <Flex.Item> + <Input withAccessory accessoryIcon="user" label="Username" onValueChange={(e) => (userName.current = e) + }></Input> + </Flex.Item> + <Spacer /> + <Flex.Item> + <Input withAccessory accessoryIcon="lock" type="password" label="Password" onValueChange={(e) => (pass.current = e) + }></Input> + </Flex.Item> + <Spacer space={4} /> + <Flex.Item align="left"> + {/* <Label text="Remember you?"> + <Check + name="demo" + value="1" + /> + </Label> */} + </Flex.Item> + <Spacer space={1} /> + <Flex.Item textAlign="center"> + <Button data-testId="submitBtn" width="full" onClick={() => { + onSubmit(userName, pass, setUser, router) + .then(result => { + if (result.data.searchScenarioByPattern) { + setUser(true) + router.replace('/dco/scenario')} + else{ + router.replace('/error') + } + }) + .then((res)=>res) + .catch(error => { router.replace('/error') }) + }}>Login</Button> + </Flex.Item> + </Flex> + </Box> + </Flex.Item> + <Flex.Item> + </Flex.Item> + </Flex> + </Box> + </Box > + </Layout> + ) +} +export default Login + + + diff --git a/developer-console-ui/app/pages/logout/index.tsx b/developer-console-ui/app/pages/logout/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f6480b20d4eb888af165eef06d3bce1e9b14a648 --- /dev/null +++ b/developer-console-ui/app/pages/logout/index.tsx @@ -0,0 +1,38 @@ +import { Box, Button, Flex, Headline, Icon, Spacer, StatusMessage, Value } from "@dco/sdv-ui" +import { useStoreState } from "easy-peasy" +import { useRouter } from "next/router"; +import Layout from "../../components/layout/layout"; +export function Logout() { + const invert = useStoreState((state: any) => state.invert) + const router=useRouter(); + return ( + <Layout> + <Box fullHeight invert={invert} variant='high' padding="large"> + <Spacer space={5} /> + <Box padding="large" > + <Flex justify="center"> + <Flex.Item> + </Flex.Item> + <Flex.Item justify-content="center" textAlign="center" align="center" > + <Box variant="body" padding="large" > + <Headline level={2}>Welcome to SDV Developer Console <Icon name='tbbui'></Icon></Headline> + <Value>Please login for best experience</Value> + </Box> + <Box variant="body" transparency="high" padding="large" > + <StatusMessage variant="success" noIcon> + You have been successfully logged out + </StatusMessage> + <Spacer space={4}></Spacer> + <Button width="compact" data-testId="loginBtn" onClick={() => { router.push('login')}}>Login</Button> + </Box> + </Flex.Item> + <Flex.Item> + </Flex.Item> + </Flex> + </Box> + </Box > + </Layout> + + ) +} +export default Logout \ No newline at end of file diff --git a/developer-console-ui/app/pages/shared/ServerSafePortal.tsx b/developer-console-ui/app/pages/shared/ServerSafePortal.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a22bce437f72cb04b3ace4e0f050760c4425995a --- /dev/null +++ b/developer-console-ui/app/pages/shared/ServerSafePortal.tsx @@ -0,0 +1,31 @@ +import { usePortalOutlet } from '@dco/sdv-ui'; +import React, { useEffect, useState } from 'react'; +import ReactDOM from 'react-dom'; + +/** + * You cannot provide the outlet as a param here as the oultet is defined + * via the PortalOutletProvider Component. Please only provide the children here. + */ +export const ServerSafePortal = ({ + children, + key, +}: { + children: React.ReactNode; + key?: string; +}) => { + const outlet = usePortalOutlet(); + const [activated, setActivated] = useState(false); + + useEffect(() => { + setActivated(true); + }, []); + + // in a server environment outlet can be null when no other target is provided + // as by default it points to document.body + // so we only render it when it is non-empty :) + // also we need to use the work-around with setState and useEffect to ensure we do not + // get hydration problems + + return <>{outlet && activated && ReactDOM.createPortal(children, outlet, key)}</>; +}; +export default ServerSafePortal; \ No newline at end of file diff --git a/developer-console-ui/app/pages/shared/conditionalAlertBtn.tsx b/developer-console-ui/app/pages/shared/conditionalAlertBtn.tsx new file mode 100644 index 0000000000000000000000000000000000000000..26844ae145c82e6bfe4a2753f3e3e7fd4c289e4a --- /dev/null +++ b/developer-console-ui/app/pages/shared/conditionalAlertBtn.tsx @@ -0,0 +1,23 @@ +import { Button, Flex, Headline, Paragraph, Popup, Spacer } from "@dco/sdv-ui"; +import { invert } from "../../services/functionShared"; + +export const ConditionalAlertBtn = ({ ...props }) => { + return <> + <Popup invert={!invert()} show={props.show} width={30} dim={true} data-testid="closeAlert" onClick={() => { props.onClose(false) }}> + <Headline>{props.popupState}</Headline> + <Paragraph>{props.popupMsg}</Paragraph> + <Spacer space={2} /> + <Flex> + <Flex.Item textAlign="right"> + <Button width="compact" variant="secondary" data-testid="closeAlert1" onClick={() => { props.onClose(false) }}> + {props.no} + </Button> + </Flex.Item> + <Flex.Item textAlign="center"> + <Button width="compact" data-testid="deleteTrack" onClick={() => { props.respectiveFun(props.respectiveId, props.mutationLoad, props.onClose) }}>{props.yes}</Button> + </Flex.Item> + </Flex> + </Popup> + </> +} +export default ConditionalAlertBtn; diff --git a/developer-console-ui/app/pages/shared/counterWithToolTip.tsx b/developer-console-ui/app/pages/shared/counterWithToolTip.tsx new file mode 100644 index 0000000000000000000000000000000000000000..18834820f9305fa947976defb99750fb0d0b2504 --- /dev/null +++ b/developer-console-ui/app/pages/shared/counterWithToolTip.tsx @@ -0,0 +1,17 @@ +import { MoreData, Tooltip } from "@dco/sdv-ui"; +import { getToolTip } from "../../services/functionTrack.service"; +import PrintTollTipValue from "./printTollTipValue"; + +export const CounterWithToolTip = ({ toolTipVal }: any) => { + const case1 = (toolTipVal.length == 1) + const case2 = (toolTipVal.length > 1) + return <> + {case1 && <PrintTollTipValue val={toolTipVal[0].country || toolTipVal[0]} />} + {case2 && <><PrintTollTipValue val={toolTipVal[0].country || toolTipVal[0]} /> < MoreData><Tooltip + inline + tooltip={getToolTip(toolTipVal.slice(1))} + >{toolTipVal.length - 1}+</Tooltip></MoreData></>} + </> +} + +export default CounterWithToolTip; \ No newline at end of file diff --git a/developer-console-ui/app/pages/shared/paginationTable.tsx b/developer-console-ui/app/pages/shared/paginationTable.tsx new file mode 100644 index 0000000000000000000000000000000000000000..404e6edb22b73dddbb835861c2b9792aa10c7e45 --- /dev/null +++ b/developer-console-ui/app/pages/shared/paginationTable.tsx @@ -0,0 +1,73 @@ +import { Button } from '@dco/sdv-ui' +import React, { useState, useEffect } from 'react' +import styles from './styles.module.css' + +export const Pagination = ({ pageChangeHandler, totalRows, rowsPerPage }: any) => { + // Calculating max number of pages + const noOfPages = Math.ceil(totalRows / rowsPerPage)||0 + + // Creating an array with length equal to no.of pages + const pagesArr = [...new Array(noOfPages)] + + // State variable to hold the current page. This value is + // passed to the callback provided by the parent + const [currentPage, setCurrentPage] = useState(1) + + // Navigation arrows enable/disable state + const [canGoBack, setCanGoBack] = useState(false) + const [canGoNext, setCanGoNext] = useState(true) + + // Onclick handlers for the butons + const onNextPage = () => setCurrentPage(currentPage + 1) + const onPrevPage = () => setCurrentPage(currentPage - 1) + const onPageSelect = (pageNo: any) => setCurrentPage(pageNo) + + // Disable previous and next buttons in the first and last page + // respectively + useEffect(() => { + if (noOfPages === currentPage) { + setCanGoNext(false) + } else { + setCanGoNext(true) + } + if (currentPage === 1) { + setCanGoBack(false) + } else { + setCanGoBack(true) + } + }, [noOfPages, currentPage]) + + // To set the starting index of the page + useEffect(() => { + pageChangeHandler(currentPage) + }, [currentPage]) + + return ( + <> + {noOfPages > 1 ? ( + <div className={styles.s}> + <div className={styles.pagebuttons}> + <Button variant='naked' data-testid="btn1" onClick={onPrevPage} disabled={!canGoBack}> + ‹ + </Button> + {pagesArr.map((num, index) => ( + <Button key={index} + variant='naked' + onClick={() => onPageSelect(index + 1)} + className={`${styles.pageBtn} ${index + 1 === currentPage ? styles.activeBtn : ''}`} + > + {index + 1} + {pagesArr[index]} + </Button> + ))} + <Button data-testid="btn" variant='naked' onClick={onNextPage} disabled={!canGoNext}> + › + </Button> + </div> + </div> + ) : null} + </> + ) +} + +export default Pagination diff --git a/developer-console-ui/app/pages/shared/printTollTipValue.tsx b/developer-console-ui/app/pages/shared/printTollTipValue.tsx new file mode 100644 index 0000000000000000000000000000000000000000..7fcb712c6b2abf42dcbd218511a58bf485e86919 --- /dev/null +++ b/developer-console-ui/app/pages/shared/printTollTipValue.tsx @@ -0,0 +1,6 @@ +import { CountryNameType } from "../../types"; + +export const PrintTollTipValue = ({ val }: CountryNameType) => { + return <>{val}</> +} +export default PrintTollTipValue; \ No newline at end of file diff --git a/developer-console-ui/app/pages/shared/status.tsx b/developer-console-ui/app/pages/shared/status.tsx new file mode 100644 index 0000000000000000000000000000000000000000..66b38b7b059d48c1939edb091259227fcf39a421 --- /dev/null +++ b/developer-console-ui/app/pages/shared/status.tsx @@ -0,0 +1,25 @@ +import { Icon, StatusMessage } from "@dco/sdv-ui"; +import { capitalizeFirstLetter } from "../../services/functionShared"; +import { StatusTypes } from "../../types"; + +export const Status = ({ status, type }: StatusTypes) => { + return <> + {/* Simulation page start */} + {status == 'Done' && type == 'SS' && <><StatusMessage variant='success'>{capitalizeFirstLetter(status).replaceAll('_', ' ')}</StatusMessage></>} + {status == 'Pending' && type == "SS" && <><StatusMessage variant='pending'>{capitalizeFirstLetter(status)}</StatusMessage></>} + {status == 'Running' && type == "SS" && <><StatusMessage variant='loading'>{capitalizeFirstLetter(status)}</StatusMessage></>} + {status == 'Timeout' && type == "SS" && <><StatusMessage variant='warning'>{capitalizeFirstLetter(status)}</StatusMessage></>} + {status == 'Error' && type == "SS" && <><StatusMessage variant='error'>{capitalizeFirstLetter(status)}</StatusMessage></>} + {/* Simulation page end */} + + {/* Vehicle details start */} + {type == "VD" && status == 'Testing' && <><StatusMessage variant='loading'>{capitalizeFirstLetter(status)}</StatusMessage></>} + {type === 'VD' && status == 'READY' && <><StatusMessage variant='success'>{capitalizeFirstLetter(status).replaceAll('_', ' ')}</StatusMessage></>} + {type === 'VD' && status == 'DRAFT' && <><Icon name='edit' /> {capitalizeFirstLetter(status)}</>} + {type === 'VD' && status == 'TEST' && <><StatusMessage variant='loading'>{capitalizeFirstLetter(status)}</StatusMessage></>} + {type === 'VD' && status == 'SUSPEND' && <><StatusMessage variant='warning'>{capitalizeFirstLetter(status)}</StatusMessage></>} + {type === 'VD' && status == 'PRODUCTION' && <><StatusMessage variant='success'>{capitalizeFirstLetter(status)}</StatusMessage></>} + {/* Vehicle details end */} + </> +} +export default Status; diff --git a/developer-console-ui/app/pages/shared/styles.module.css b/developer-console-ui/app/pages/shared/styles.module.css new file mode 100644 index 0000000000000000000000000000000000000000..e9c2950fe6c5dc0f3e58b6bb4ec66cf992b29bef --- /dev/null +++ b/developer-console-ui/app/pages/shared/styles.module.css @@ -0,0 +1,39 @@ +/* used for current pagination style */ + +.pagination { + margin: 30px 35px 0; + text-align: right; + display: flex; + justify-content: space-between; + align-items: baseline; +} + +.pageInfo { + /* color: transparent; */ + font-size: 0.874em; + letter-spacing: 0.5px; +} + +.pageButtons { + display: flex; +} + +.pageBtn { + margin: 5px; + width: 35px; + height: 35px; + font-weight: normal; + font-size: 15px; +} + +.activeBtn { + border: 1px solid rgba(226, 0, 116, 1); + color: rgba(226, 0, 116, 1); + background-color: transparent; +} + +.disabledPageBtn { + background-color: #a0a3bd; + cursor: not-allowed; + opacity: 0.5; +} \ No newline at end of file diff --git a/developer-console-ui/app/pages/shared/tagLists.tsx b/developer-console-ui/app/pages/shared/tagLists.tsx new file mode 100644 index 0000000000000000000000000000000000000000..7c5d3ee1f709182663808e6f603e81a1d3fa1098 --- /dev/null +++ b/developer-console-ui/app/pages/shared/tagLists.tsx @@ -0,0 +1,18 @@ +import { Tag } from "@dco/sdv-ui"; +import { displayBrandVal } from "../../services/functionShared"; + +export const TagLists = ({ data, type, from }: any) => { + return <> + {from == 'relaseDetails' && data?.getReleaseById?.[type]?.filter((v: any, i: any, a: any) => a.indexOf(v) === i).map((x: any) => { + return (<Tag icon={type == 'brands' ? displayBrandVal(x) : ''} text={x} key={x} className={""} />) + })} + {from == 'trackDetails' && type != 'compNVersion' && data.map((x: any) => { + return (<Tag icon={type == 'brands' ? displayBrandVal(x) : ''} text={x} key={x} className={""} />) + })} + {from == 'trackDetails' && type == 'compNVersion' && data.map((x: any) => { + return (<Tag text={x.name + ", " + x.version} key={x} className={""} />) + })} + + </> +} +export default TagLists; \ No newline at end of file diff --git a/developer-console-ui/app/public/favicon.ico b/developer-console-ui/app/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..718d6fea4835ec2d246af9800eddb7ffb276240c Binary files /dev/null and b/developer-console-ui/app/public/favicon.ico differ diff --git a/developer-console-ui/app/services/credentials.service.ts b/developer-console-ui/app/services/credentials.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..734b249a73ade74958a8a6b00b860ad2e3abd03a --- /dev/null +++ b/developer-console-ui/app/services/credentials.service.ts @@ -0,0 +1,37 @@ +import { Link } from "../libs/apollo" +import { GET_SCENARIO } from "./queries" + +export const onSubmit = async (userName: any, pass: any, setUser: Function, router: any) => { + { + + let username = userName.current + let password = pass.current + var decodedStringBtoA = `${username}:${password}` + var encodedStringBtoA = Buffer.from(decodedStringBtoA).toString('base64') + localStorage.setItem('token', encodedStringBtoA); + localStorage.setItem('user', userName.current); + const token = localStorage.getItem('token'); + return fetch(Link, { + method: 'POST', + headers: { + 'content-type': 'application/json', + 'Authorization': token ? `Basic ${token}` : "", + }, + body: JSON.stringify({ + query: GET_SCENARIO, + variables: { + scenarioPattern: '', + page: 0, + size: 10, + }, + }), + }) + .then((res) => res.json()) + .then((result) =>result) + .catch((error) => { + router.replace('/error') + localStorage.removeItem('token') + setUser(false) + }) + } +} \ No newline at end of file diff --git a/developer-console-ui/app/services/functionScenario.services.ts b/developer-console-ui/app/services/functionScenario.services.ts new file mode 100644 index 0000000000000000000000000000000000000000..999cabd2d6a8caed3e5a9a9ed741c7d4878c63a9 --- /dev/null +++ b/developer-console-ui/app/services/functionScenario.services.ts @@ -0,0 +1,346 @@ +import { gql } from '@apollo/client' +import axios from 'axios' +import { Link } from '../libs/apollo' +import { GET_SCENARIO } from './queries' +import { setTimeOutFunction } from './functionShared' + +// scenario active and archived tab data start**** +export const libRowData = (rawData: any) => + rawData?.data?.searchScenarioByPattern?.content?.map((item: any) => { + return { + check: '', + sid: item.id, + scenario: item.name, + type: item.type, + filename: item.file.path.substring(item.file.path.lastIndexOf('/') + 1), + createdBy: item.createdBy, + description: item.description, + lastUpdated: + new Date(item.lastModifiedAt).toLocaleDateString() + ', ' + new Date(item.lastModifiedAt).toLocaleTimeString(), + menu: '', + } + }) +export const getLibData = async (pageNo: any, searchval: any) => { + if (searchval != '') { + pageNo = 1 + } + const token = localStorage.getItem('token'); + return fetch(Link, { + method: 'POST', + headers: { + 'content-type': 'application/json', + 'Authorization': token ? `Basic ${token}` : "", + }, + body: JSON.stringify({ + query: GET_SCENARIO, + variables: { + scenarioPattern: searchval, + page: pageNo - 1, + size: 10, + }, + }), + }) + .then((res) => res.json()) + .then((result) => result) + .catch((error) => { + console.log('Error fetching data:::', error.message) + }) +} +// scenario active and archived tab data end**** + +export const callUploadAxiosAPIForNewScenario = async (values: any, sessionUser: any) => { + try { + const token = localStorage.getItem('token'); + return await axios.post( + Link, + getUploadFormDataForNewScenario( + values.selectedUploadFile, + values.name, + values.type, + values.description, + sessionUser + ), + { + headers: { + 'content-type': 'application/json', + 'Authorization': token ? `Basic ${token}` : "", + }, + } + ) + } catch (e) { + return { data: { errors: [{ message: '' }] } } + } +} +export const callUploadAxiosAPIForUpdateScenario = async (values: any, sessionUser: any) => { + try { + const token = localStorage.getItem('token'); + return await axios.post( + Link, + getUploadFormDataForUpdateScenario( + values.sid, + values.selectedUploadFile, + values.name, + values.type, + values.description, + sessionUser + ), + { + headers: { + 'content-type': 'application/json', + 'Authorization': token ? `Basic ${token}` : "", + }, + } + ) + } catch (e) { + return { data: { errors: [{ message: '' }] } } + } +} +export function getUploadFormDataForNewScenario( + selectedUploadFile: any, + name: string, + type: string, + description: string, + sessionUser: any +) { + let formData = new FormData() + if (description === undefined) { + description = '' + } + formData.append( + 'operations', + `{ "query": "mutation CREATE_SCENARIO($file: Upload!, $scenarioInput: ScenarioInput) {createScenario(file: $file, scenarioInput: $scenarioInput)}" , "variables": {"file": null, "scenarioInput": {"name": "${name}", "type": "${type}", "status": "CREATED", "description": "${description}", "lastModifiedBy": "${sessionUser}", "createdBy": "${sessionUser}"}}}` + ) + if (selectedUploadFile) { + formData.append('file', selectedUploadFile[0]) + } + formData.append('map', '{"file": ["variables.file"]}') + return formData +} +export function getUploadFormDataForUpdateScenario( + sid: any, + selectedUploadFile: any, + name: string, + type: string, + description: string, + sessionUser: any +) { + let formData = new FormData() + formData.append( + 'operations', + `{ "query": "mutation UPDATE_SCENARIO($id: ID!, $file: Upload, $scenarioInput: ScenarioInput){updateScenario(id: $id, file: $file, scenarioInput: $scenarioInput)}" , "variables": {"id":"${sid}", "file": null, "scenarioInput": {"name": "${name}", "type": "${type}", "status": "CREATED", "description": "${description}", "lastModifiedBy": "${sessionUser}", "createdBy": null}}}` + ) + if (selectedUploadFile) { + formData.append('file', selectedUploadFile[0]) + } + formData.append('map', '{"file": ["variables.file"]}') + return formData +} +export function handleNewScenarioSubmitInService( + values: any, + sessionUser: any, + setFunctions: { + setName: Function + setType: Function + setDescription: Function + setUploadFile: Function + setFileSizeError: Function + setFileNameError: Function + setToastMsg: Function + setNameError: Function + setTypeError: Function + setFileError: Function + }, + setToastOpen: Function, + onClose: any +) { + if (values.name && values.type && values.selectedUploadFile) { + let flag: any = callUploadAxiosAPIForNewScenario(values, sessionUser) + flag.then((e: any) => { + setFunctions.setUploadFile() + setToastMessageForNewScenario(e.data, setFunctions.setToastMsg, onClose, setToastOpen, setFunctions) + setToastOpen(true) + }) + } else { + !values.name && setFunctions.setNameError(true) + !values.type && setFunctions.setTypeError(true) + !values.selectedUploadFile && setFunctions.setFileError(true) + } +} +export function handleUpdateScenarioSubmitInService( + values: any, + sessionUser: any, + setFunctions: { + setName: Function + setType: Function + setDescription: Function + setUploadFile: Function + setFileSizeError: Function + setFileNameError: Function + setSuccessMsgScenario: Function + setNameError: Function + setTypeError: Function + setFileError: Function + }, + setToastOpenScenario: Function, + onClose: any, + router: { asPath: any; replace: Function } +) { + if (values.name && values.type) { + let flag: any = callUploadAxiosAPIForUpdateScenario(values, sessionUser) + flag.then((e: any) => { + setFunctions.setUploadFile() + setToastMessageForUpdateScenario(e.data, setFunctions.setSuccessMsgScenario, onClose, setToastOpenScenario) + setToastOpenScenario(true) + router.replace(router.asPath) + }) + } else { + !values.name && setFunctions.setNameError(true) + !values.type && setFunctions.setTypeError(true) + !values.selectedUploadFile && setFunctions.setFileError(false) + } +} +export function resetFormForScenario(setFunctions: any) { + setFunctions.setName('') + setFunctions.setType('') + setFunctions.setDescription('') + setFunctions.setFileSizeError(false) + setFunctions.setFileNameError(false) +} +export function scenarioDataFromMap(data: any) { + return data?.data?.searchScenarioByPattern?.content?.map((item: any) => { + return { + sid: item.id, + scenario: item.name, + type: item.type, + filename: item.files?.path.substring(item.files?.path.lastIndexOf('/') + 1), + createdBy: item.createdBy, + description: item.description, + lastUpdated: + new Date(item.lastModifiedAt).toLocaleDateString() + ', ' + new Date(item.lastModifiedAt).toLocaleTimeString(), + delete: '', + } + }) +} +export function onClickScenario(sid: any, deleteScenario: any, setShowAlert: Function) { + deleteScenario({ + variables: { + id: sid, + }, + }) + setShowAlert(false) +} +export function setToastMessageForDeleteScenario( + data: any, + setToastMsg: Function, + setToastOpen: Function, + type: string +) { + setToastOpen(true) + setTimeout(() => { + setToastOpen(false) + }, 4000) + if (type == 'success') { + setToastMsg(data.deleteScenarioById) + setTimeout(() => { + window.location.reload() + }, 3000) + return true + } else { + setToastMsg(data.message) + return false + } +} +export function setToastMessageForNewScenario( + data: any, + setToastMsg: Function, + props: any, + setToastOpen: Function, + setFunctions: any +) { + if (data.data?.createScenario) { + resetFormForScenario(setFunctions) + props(false) + setTimeOutFunction(setToastOpen, 3000) + setToastMsg('Scenario has been created successfully') + setTimeout(() => { + window.location.reload() + }, 3000) + return false + } else { + resetFormForScenario(setFunctions) + props(false) + setTimeOutFunction(setToastOpen, 3000) + setToastMsg('Can not create New Scenario, Please try again later') + return true + } +} +export function setToastMessageForUpdateScenario( + data: any, + setSuccessMsgScenario: Function, + props: any, + setToastOpenScenario: Function +) { + if (data.data?.updateScenario) { + setToastOpenScenario(true) + setSuccessMsgScenario('Scenario has been updated successfully') + props(false) + setTimeOutFunction(setToastOpenScenario, 3000) + setTimeout(() => { + window.location.reload() + }, 1000) + return false + } +} +export const DELETE_SCENARIO = gql` + mutation DELETE_SCENARIO($id: ID!) { + deleteScenarioById(id: $id) + } +` +export function getFileSIzeInService( + e: any, + setUploadFile: Function, + uploadFile: Function, + maxFileSizeInMB: number, + minFileSizeInMB: number, + setFileSizeError: Function, + setFileNameError: Function +) { + // file size check + if (e.target.value.includes('.txt') || e.target.value.includes('.odx')) { + setUploadFile(e.target.files) + let size = uploadFile(e.target.files) + let sizeOfFile = e.target.files[0].size + uploadFileCondition(size, sizeOfFile, maxFileSizeInMB, minFileSizeInMB, setFileSizeError, e, setUploadFile) + } else { + e.target.value = null + setUploadFile(undefined) + } + + // filename validity check + if (/^[a-z0-9_.@()-]+\.[^.]+$/i.test(e?.target?.files[0]?.name)) { + setFileNameError(false) + } else { + e.target.value = null + setFileNameError(true) + setUploadFile(undefined) + } +} +export function uploadFileCondition( + size: any, + sizeOfFile: any, + maxFileSizeInMB: any, + minFileSizeInMB: any, + setFileSizeError: Function, + e: any, + setUploadFile: Function +) { + if (sizeOfFile <= minFileSizeInMB || sizeOfFile > maxFileSizeInMB) { + setFileSizeError(true) + e.target.value = null + setUploadFile(undefined) + return true + } else { + setFileSizeError(false) + return false + } +} diff --git a/developer-console-ui/app/services/functionShared.ts b/developer-console-ui/app/services/functionShared.ts new file mode 100644 index 0000000000000000000000000000000000000000..75ee2c257b053c0babcfc4ca45d34d1d8d34dcf9 --- /dev/null +++ b/developer-console-ui/app/services/functionShared.ts @@ -0,0 +1,85 @@ +import { useStoreState } from 'easy-peasy' + +export function setTimeOutFunction(setToastOpen: Function, timer: number) { + setTimeout(() => { + setToastOpen(false) + }, timer) +} +export function onLoadMore(setPageSize: Function, setPage: Function, getVehicle: Function, pageSize: number, page: number) { + setPageSize(pageSize) + setPage(page + 1) + getVehicle() +} +export function capitalizeFirstLetter(status: string) { + return status[0].toUpperCase() + status.slice(1) +} +export function displayBrandVal(brand: string) { + if (brand == 'Audi') { + return 'logo-audi' + } + if (brand == 'Mercedes' || brand == 'Mercedes Benz' || brand == 'Mercedes-Benz') { + return 'mercedes' + } + if (brand == 'BMW') { + return 'logo-bmw' + } + if (brand == 'eGO') { + return 'logo-ego' + } + if (brand == 'Ford') { + return 'logo-ford' + } + if (brand == 'Mitsubishi') { + return 'logo-mitsubishi' + } + if (brand == 'Nissan') { + return 'logo-nissan' + } + if (brand == 'Porsche') { + return 'logo-porsche' + } + if (brand == 'Renault') { + return 'logo-renault' + } + if (brand == 'Skoda') { + return 'logo-skoda' + } + if (brand == 'Volvo') { + return 'logo-volvo' + } + if (brand == 'VW' || brand == 'Volkswagen') { + return 'logo-vw' + } + return 'vehicle' +} +export function getValArray(datas: any, objName: string) { + let temp = [ + ...new Set( + datas?.map((val: any) => { + return val[objName] + }) + ), + ] + temp = temp + .filter(function (element) { + return element !== null + }) + .filter((v, i, a) => a.indexOf(v) === i) + return temp +} +export function invert() { + return useStoreState((state: any) => state.invert) +} +export function avoidSplChars(e: any) { + var bad = /[^\sa-z\d]/i, + key = String.fromCharCode(e.keyCode || e.which) + if (e.which !== 0 && e.charCode !== 0 && bad.test(key)) { + e.returnValue = false + if (e.preventDefault) { + return e.preventDefault() + } + } +} +export function checkRoute(path:string,router:any, pathname:any){ + return (router?.pathname?.includes(path) ? pathname : path) +} diff --git a/developer-console-ui/app/services/functionSimulation.service.tsx b/developer-console-ui/app/services/functionSimulation.service.tsx new file mode 100644 index 0000000000000000000000000000000000000000..733bfde8fb95fe4450e8b8e66c3f25b363549a6e --- /dev/null +++ b/developer-console-ui/app/services/functionSimulation.service.tsx @@ -0,0 +1,113 @@ +import router from "next/router"; +import { Link } from '../libs/apollo' +import { ClearAllTypes, RawDataSimType } from "../types"; +import { GET_SIMULATIONS } from "./queries"; +// Simulation data start**** +export const simRowData = (rawData: RawDataSimType) => + rawData?.data?.simulationReadByQuery?.content?.map((item: any) => { + return { + name: item.name, + status: item.status, + numberVehicles: item.noOfVehicle, + brand: item.brands, + type: item.scenarioType, + numberScenarios: item.noOfScenarios, + platform: item.platform, + env: item.environment, + date: new Date(item.startDate).toLocaleDateString() + + ', ' + + new Date(item.startDate).toLocaleTimeString(), + } + }) +export const getSimData = async (pageNo: number) => { + const token = localStorage.getItem('token'); + return fetch(Link, { + method: 'POST', + headers: { + 'content-type': 'application/json', + 'Authorization': token ? `Basic ${token}` : "", + }, + body: JSON.stringify({ + query: GET_SIMULATIONS, + variables: { + search: null, + query: null, + page: pageNo - 1, + size: 10, + sort: "DESC" + }, + }), + }) + .then((res) => res.json()) + .then((result) => result).catch(error => { + console.log("Error fetching data:::", error.message); + }) +} +// Simulation data end**** + +export function launchSimulation(variable: any, createSimulation: Function, setVariable: any) { + variable.scenario = variable.scenario.filter((c: any) => { if (c.checked) { return c.id } }).map((l: any) => l.id) + variable.track = variable.track.filter((c: any) => { if (c.checked) { return c.id } }).map((l: any) => l.id) + if (variable.title && variable.scenarioType && variable.scenario.length != 0 && variable.track.length != 0) { + createSimulation({ + variables: { + simulationInput: { + name: variable.title, + environment: variable.environment, + description: variable.description, + platform: variable.platform, + scenarioType: variable.scenarioType, + hardware: variable.hardware, + tracks: variable.track, + scenarios: variable.scenario, + createdBy: "abc@t-systems.com" + }, + }, + }) + } else { + setVariable.setTitleError(true) + setVariable.setSTypeError(true) + setVariable.setTrackError(true) + setVariable.setScenarioError(true) + } +} +export function onLaunchedSimulation(setSelectedscenario: Function, setSelectedtrack: Function, setIsToastOpen: Function, setToastMsg: Function, res: any, flag: boolean) { + if (flag) { + setIsToastOpen(true) + setToastMsg('Simulation has been launched successfully') + setTimeout(() => { + router.push('/dco/simulation') + setSelectedscenario([{ id: '1234', checked: false }]) + setSelectedtrack([{ id: '5678', checked: false }]) + }, 2500) + + } else { + setIsToastOpen(true) + setToastMsg(JSON.parse(JSON.stringify(res)).message) + setTimeout(() => { + router.push('/dco/simulation') + setSelectedscenario([{ id: '1234', checked: false }]) + setSelectedtrack([{ id: '5678', checked: false }]) + }, 3000) + } +} +export function clearAll(setVariable: ClearAllTypes) { + setVariable.setTitle(''); + setVariable.setDescription(''); + setVariable.setEnvironment(''); + setVariable.setPlatform(''); + setVariable.setSelectedscenario([{ id: '1234', checked: false }]); + setVariable.setSelectedtrack([{ id: '5678', checked: false }]); + setVariable.setScenarioType(''); + setVariable.setHardware(''); + setVariable.setSearchval(''); + setVariable.setTitleError(false); + setVariable.setSTypeError(false); + setVariable.setTrackError(false); + setVariable.setScenarioError(false); +} +export function onClickNewSimulation() { + setTimeout(() => { + router.push('/dco/addSimulation') + }, 0) +} \ No newline at end of file diff --git a/developer-console-ui/app/services/functionTrack.service.ts b/developer-console-ui/app/services/functionTrack.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..58ad1844ed86f0c92e8ca52ff7bc9e0cfd4f5058 --- /dev/null +++ b/developer-console-ui/app/services/functionTrack.service.ts @@ -0,0 +1,281 @@ +import router from 'next/router' +import { Link } from '../libs/apollo' +import { LIST_TRACKS } from './queries' +import { setTimeOutFunction } from './functionShared' +import { + ComponentTypes, + GetVehicleListType, + RawDataTrackType, + VehicleTypes, +} from '../types' +import { useStoreState } from 'easy-peasy' +// track tab data start**** +export const trackRowData = (rawData: RawDataTrackType) => + rawData?.data?.searchTrackByPattern?.content?.map((item: any) => { + return { + check: '', + trackID: item.id, + trackName: item.name, + trackNameSim: item.name, + trackStatus: item.state, + trackType: item.trackType, + numberofvehicles: item.vehicles.length, + country: [...new Set(item.vehicles.map((items: any) => items.country))], + delete: '', + } + }) +export const getTrackData = async (pageNo: any, searchval: any) => { + if (searchval != '') { + pageNo = 1 + } + const token = localStorage.getItem('token'); + return fetch(Link, { + method: 'POST', + mode: 'cors', + headers: { + 'content-type': 'application/json', + 'Authorization': token ? `Basic ${token}` : "", + }, + body: JSON.stringify({ + query: LIST_TRACKS, + variables: { trackPattern: searchval, page: pageNo - 1, size: 10 }, + }), + }) + .then((res) => res.json()) + .then((result) => result) + .catch((error) => { + console.log('Error fetching data:::', error.message) + }) +} +// track tab data end**** +export function Selectedtrack() { + return useStoreState((state: any) => state.selectedtrack) +} +export function Searchval() { + return useStoreState((state: any) => state.searchval); +} +export function uploadFile(files: any) { + let fileSize: any = 0 + for (let val of files) { + fileSize += val.size + } + fileSize = (fileSize / 1024 / 1024).toFixed(2) + return parseFloat(fileSize) +} +export function onClickDeleteTrack(trackID: string, deleteTrack: Function, setShowAlert: Function) { + deleteTrack({ + variables: { + id: trackID, + }, + }) + setShowAlert(false) +} +export function setToastMessageForDeleteTrack(data: any, setToastMsg: Function, setToastOpen: Function, type: string) { + setToastOpen(true) + setTimeout(() => { + setToastOpen(false) + }, 2000) + if (type == 'success') { + setTimeOutFunction(setToastOpen, 3000) + setToastMsg('Track has been deleted successfully') + setTimeout(() => { + window.location.reload() + }, 2000) + return true + } else { + setTimeOutFunction(setToastOpen, 3000) + setToastMsg(data.message) + return false + } +} +export function getToolTip(country: any) { + if (country.length == 1) { + return country[0].country || country[0] + } else { + return country.reduce((a: any, b: any) => { + return (a.country || a) + ', ' + (b.country || b) + }) + } +} +export function onselectionchange( + selectedRows: any, + setArrVehicle: Function, + vehicles: any, + onSelectionChangedFun: any, + gridRef: any, + setonSelectedRows: Function +) { + setArrVehicle([]) + selectedRows.map((val: { data: { vin: any; country: any } }) => { + vehicles.push({ + vin: val.data.vin, + country: val.data.country, + }) + onSelectionChangedFun(gridRef, setonSelectedRows) + return setArrVehicle(vehicles) + }) +} +export function saveNewTrack(title: string, arrVehicle: any, duration: string, description: string, createTrack: Function, setVariable: any) { + if (title && arrVehicle.length != 0 && duration) { + createTrack({ + variables: { + trackInput: { + name: title, + description: description, + trackType: 'Test', + duration: duration, + vehicles: arrVehicle, + state: 'CREATED', + }, + }, + }) + } else { + setVariable.setTitleError(true) + setVariable.setDurationError(true) + setVariable.setVehicleError(true) + } +} +export function onClickTrackColumn(index: any, setTname: Function, setTid: Function) { + setTname(index?.row?.values?.trackName) + setTid(index?.row?.values?.trackID) + router.push(`/dco/tracksMain/trackInfo/trackVehicleDetails`) +} +export function mapDataToTable(data: any, setDatas: any, datas: any) { + let mapDatas: any + mapDatas = data?.vehicleReadByQuery?.content?.map((val: any) => { + return { + vin: val?.vin, + type: val?.type, + lastconnected: val?.updatedAt, + country: val?.country, + brand: val?.brand, + model: val?.model, + status: val?.status, + } + }) + if ((datas.length != 0 || datas.length == 0) && mapDatas) { + setDatas([...datas, ...mapDatas]) + } +} +export function deleteTrackFun( + setShowAlert: Function, + setdeleteTrackId: Function, + isShowAlert: boolean, + trackId: string +) { + setShowAlert(!isShowAlert) + setdeleteTrackId(trackId) +} +export function getCompNVersion(deviceArr: any, arrType: any, arrComponent: any) { + deviceArr?.map((val: any) => { + val.map((x: any) => { + arrComponent.push(x.components) + arrType.push(x.id) + }) + }) + return arrType +} +export function getDevices(arrComponent: any, compVal: any) { + arrComponent?.map((val: any) => { + val.map((x: any) => { + compVal.push({ name: x.name, version: x.version }) + }) + }) + return compVal +} +export function onSelectionChanged(gridRef: any, setonSelectedRows: Function) { + const checkedRows = gridRef.current!.api.getSelectedRows() + if (checkedRows) { + setonSelectedRows([...checkedRows]) + } +} +export function selectedCheckboxFunction(onSelectedRows: any, gridRef: any) { + let temp = onSelectedRows.map((data: any) => { + return data.vin + }) + if (temp.length != 0) { + gridRef.current!.api && + gridRef.current!.api.forEachNode((node: any) => { + temp.includes(node.data['vin']) ? node.setSelected(true) : node.setSelected(false) + }) + } +} +export function getUploadFormDataForNewComponent( + selectedUploadFile: any, + deviceValue: [], + componentValue: string, + typeValue: string, + versionValue: string, + sessionUser: any, + HWValue: any +) { + let formData = new FormData() + // formData.append('operations', `{\"operationName\": \"CREATE_COMPONENT\",\"variables\":{},\"query\": \"mutation CREATE_COMPONENT {\\n createComponent(componentInput: {name: \\"${componentValue}\\", type: \\"${typeValue}\\", status: DRAFT, version: \\"${versionValue}\\", updatedBy: \\"${sessionUser}\\", targetHardwareModule: [\\"${HWValue}\\"], targetDevices: [\\"${deviceValue}\\"]})\\n}\\n"}`) + formData.append( + 'operations', + `{ "query": "mutation CREATE_COMPONENT($file: Upload!, $componentInput: ComponentInput) {createComponent(file: $file, componentInput: $componentInput)}" , "variables": {"file": null, "componentInput": {"name": "${componentValue}", "type": "${typeValue}", "status": "DRAFT", "version": "${versionValue}", "targetDevices": ["${deviceValue}"], "targetHardwareModule": ["${HWValue}"], "updatedBy": "${sessionUser}"}}}` + ) + if (selectedUploadFile) { + formData.append('file', selectedUploadFile[0]) + } + formData.append('map', '{"file": ["variables.file"]}') + + return formData +} +export function onClickMenuItem(setShowAlert: Function) { + setShowAlert(true) +} +export function onCompletedCreateTrack( + setButtonDisable: any, + setIsToastOpen: any, + setToastMsg: any, + res: any, + flag: boolean +) { + if (flag) { + setButtonDisable(true) + setIsToastOpen(true) + setToastMsg('Track has been added successfully') + setTimeout(() => { + router.push('/dco/tracksMain') + }, 2500) + } else { + setButtonDisable(false) + setIsToastOpen(true) + setToastMsg(JSON.parse(JSON.stringify(res)).message) + setTimeout(() => { + setIsToastOpen(false) + router.push('/dco/tracksMain') + }, 6000) + } +} +export function onClickNewTrack(setCompid: Function) { + setTimeout(() => { + router.push('/dco/addTrack') + setCompid('') + }, 0) +} +export function getVehicleList(data: GetVehicleListType) { + return data?.findTrackById?.vehicles?.map((val: VehicleTypes) => { + return { + vin: val.vin, + type: val.type, + lastconnected: val.updatedAt, + country: val.country, + status: val.status, + brand: val.brand || undefined, + devices: val?.devices?.map((x: any) => { + return { + type: x.type, + id: x.id, + components: x?.components.map((y: ComponentTypes) => { + return { + name: y.name, + version: y.version, + } + }), + } + }), + } + }) +} diff --git a/developer-console-ui/app/services/queries.ts b/developer-console-ui/app/services/queries.ts new file mode 100644 index 0000000000000000000000000000000000000000..064dc658e16316fdad6d5d53a33582d3143e1d83 --- /dev/null +++ b/developer-console-ui/app/services/queries.ts @@ -0,0 +1,208 @@ +import { gql } from '@apollo/client' +// scenario start +export const GET_SCENARIO = ` + query SEARCH_SCENARIO( $scenarioPattern: String, $page: Int, $size: Int) { + searchScenarioByPattern(scenarioPattern: $scenarioPattern,page: $page, size: $size) { + content { + id + name + description + type + status + createdBy + createdAt + lastModifiedBy + lastModifiedAt + file { + id + path + size + checksum + updatedBy + updatedOn + } + } + empty + first + last + page + size + pages + elements + total + } + } +` +// scenario end + +// tracks start +export const LIST_TRACKS = `query SEARCH_TRACK($trackPattern: String, $page: Int, $size: Int) { + searchTrackByPattern(trackPattern: $trackPattern, page: $page, size: $size) { + content{ + id + name + state + trackType + vehicles { + vin + country + } + } + empty + first + last + page + size + pages + elements + total + } + } + ` +export const VEHICLE_LIST = gql` + query LIST_VEHICLE($search: String, $query: String, $page: Int, $size: Int, $sort: [String]) { + vehicleReadByQuery(search: $search, query: $query, page: $page, size: $size, sort: $sort) { + content { + vin + owner + ecomDate + country + model + brand + region + instantiatedAt + createdAt + updatedAt + status + type + fleets { + id + name + type + } + services { + serviceId + operation + updatedAt + } + devices { + id + type + status + createdAt + gatewayId + dmProtocol + modifiedAt + dmProtocolVersion + } + } + empty + first + last + page + size + pages + elements + total + } + } +` +export const DELETE_TRACK = gql` + mutation DELETE_TRACK($id: ID!) { + deleteTrackById(id: $id) + } +` +export const TRACK_DETAILS = gql` + query TRACK_BY_ID($id: ID!) { + findTrackById(id: $id) { + id + name + state + trackType + duration + description + vehicles { + vin + country + brand + status + updatedAt + type + devices { + id + type + status + createdAt + gatewayId + modelType + dmProtocol + modifiedAt + dmProtocolVersion + serialNumber + components { + id + name + status + version + environmentType + } + } + } + } + } +` +export const CREATE_TRACK = gql` + mutation CREATE_TRACK($trackInput: TrackInput) { + createTrack(trackInput: $trackInput) { + id + name + trackType + state + description + duration + } + } +` +// tracks end + +// simulation start +export const GET_SIMULATIONS = ` + query LIST_SIMULATION($search: String, $query: String, $page: Int, $size: Int, $sort: [String]) { + simulationReadByQuery(search: $search, query: $query, page: $page, size: $size, sort: $sort){ + content { + id + name + status + environment + platform + scenarioType + noOfVehicle + noOfScenarios + brands + hardware + description + createdBy + startDate + } + empty + first + last + page + size + pages + elements + total + } + } +` +export const LAUNCH_SIMULATION = gql` + mutation LAUNCH_SIMULATION($simulationInput: SimulationInput) { + launchSimulation(simulationInput: $simulationInput) + } +` +export const HARDWARE_MODULE = gql` + query GET_HARDWARE_MODULE { + getHardwareModule + } +` +// simulation end diff --git a/developer-console-ui/app/services/store.service.ts b/developer-console-ui/app/services/store.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..ad4e8de90e7e339d87ea3d3ef595dd615ba6a78d --- /dev/null +++ b/developer-console-ui/app/services/store.service.ts @@ -0,0 +1,45 @@ +import { action, createStore, persist } from "easy-peasy"; + +export const store = createStore(persist({ + invert: true, + setInvert: action((state: any, payload: any) => { + state.invert = payload; + }), + count: 0, + setCount: action((state: any, payload: any) => { + state.count = payload; + }), + tname: 'Track', + setTname: action((state: any, payload: any) => { + state.tname = payload; + }), + tid: '0', + setTid: action((state: any, payload: any) => { + state.tid = payload; + }), + compid: '', + setCompid: action((state: any, payload: any) => { + state.compid = payload; + }), + + page: '', + setPage: action((state: any, payload: any) => { + state.setPage = payload; + }), + selectedscenario: [{id:'1234',checked:false}], + setSelectedscenario: action((state: any, payload: any) => { + state.selectedscenario = payload; + }), + selectedtrack: [{id:'5678',checked:false}], + setSelectedtrack: action((state: any, payload: any) => { + state.selectedtrack = payload; + }), + searchval:"", + setSearchval: action((state: any, payload: any) => { + state.searchval = payload; + }), + user:false, + setUser:action((state: any, payload: any) => { + state.user = payload; + }), +})); diff --git a/developer-console-ui/app/sonar.js b/developer-console-ui/app/sonar.js new file mode 100644 index 0000000000000000000000000000000000000000..9e0ffa071231513a43089cf7b59824b562b7b2cc --- /dev/null +++ b/developer-console-ui/app/sonar.js @@ -0,0 +1,18 @@ +const scanner = require('sonarqube-scanner') + +scanner( + { + serverUrl: process.env.SONAR_URL, + token: process.env.SONAR_TOKEN, + options: { + 'sonar.projectKey': 'developer-console-ui', + 'sonar.projectName': 'developer-console-ui', + 'sonar.projectVersion': process.env.APP_VERSION, + 'sonar.sources': '.', + 'sonar.exclusions': '**/__tests__/**/*, **/components/auth/ForceSession.tsx, **/pages/api/**,**/pages/shared/ServerSafePortal.tsx, **/pages/hypercube/**, **/next.config.js, **/jest.config.js, **/sonar.js, **/libs/config.ts, **/assets/constants/constants.js, **/libs/oauth2.ts, **/pages/_app.tsx, **/libs/apollo.ts, **/pages/hypercube/componentNameFinderDropdown.tsx', + 'sonar.coverage.exclusion': '**/__tests__/**/*, **/components/auth/ForceSession.tsx, **/pages/api/**, **/pages/hypercube/**, **/next.config.js, **/jest.config.js, **/sonar.js, **/libs/config.ts, **/assets/constants/constants.js, **/libs/oauth2.ts, **/pages/_app.tsx, **/libs/apollo.ts, **/pages/hypercube/componentNameFinderDropdown.tsx,**/pages/shared/ServerSafePortal.tsx', + 'sonar.test.exclusion': '**/__tests__/**/*, **/components/auth/ForceSession.tsx, **/pages/api/**, **/pages/hypercube/**, **/next.config.js, **/jest.config.js, **/sonar.js, **/libs/config.ts, **/assets/constants/constants.js, **/libs/oauth2.ts, **/pages/_app.tsx, **/libs/apollo.ts, **/pages/hypercube/componentNameFinderDropdown.tsx,**/pages/shared/ServerSafePortal.tsx' + }, + }, + () => process.exit() +) diff --git a/developer-console-ui/app/styles/globals.css b/developer-console-ui/app/styles/globals.css new file mode 100644 index 0000000000000000000000000000000000000000..34d6a0d62e91bbe3f61b9a2c146003fd1b761d53 --- /dev/null +++ b/developer-console-ui/app/styles/globals.css @@ -0,0 +1,60 @@ +html, +body { + padding: 0; + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, + Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; +} + +a { + color: inherit; + text-decoration: none; +} + +* { + box-sizing: border-box; +} + +.button-green:hover { + background-color: rgba(0, 221, 102, 0.3) !important; +} + +.button-blue:hover { + background-color: rgba(138, 199, 230, 0.8)!important; +} + +.button-magenta:hover { + background-color: rgba(226, 0, 116, 0.2)!important; +} + + +/* @keyframes fa-spinner { + to {transform: rotate(360deg) !important;} +} + +.fa-spinner:before { + content: '' !important; + box-sizing: border-box !important; + position: absolute !important; + top: 50% !important; + left: 50% !important; + width: 20px !important; + height: 20px !important; + margin-top: -10px !important; + margin-left: -10px !important; + border-radius: 50% !important; + border: 2px solid #ccc !important; + border-top-color: #e20074!important; + animation: spinner .4s linear infinite !important; +} */ + +/* // grid table css */ +.agGrid .ag-cell-wrap-text { + white-space: nowrap !important; +} +.grid-table .ag-cell { + align-items: baseline !important; + align-content: center !important; + height: auto !important; + +} \ No newline at end of file diff --git a/developer-console-ui/app/tsconfig.json b/developer-console-ui/app/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..2cba066254c11d42065fbdb09a6aa2e1a0388572 --- /dev/null +++ b/developer-console-ui/app/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "downlevelIteration":true + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ], + "exclude": [ + "node_modules" + ] +} diff --git a/developer-console-ui/app/types/index.tsx b/developer-console-ui/app/types/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ecf1adfbc071af823200d128ca391cce36799c22 --- /dev/null +++ b/developer-console-ui/app/types/index.tsx @@ -0,0 +1,133 @@ +export interface BoxToastProps { + toastMsg: string; +} +export interface StatusTypes { + status: string; + type: string; +} +export interface CountryNameType { + val: string; +} +// clear all fields in simulation +export interface ClearAllTypes { + setTitle: Function; + setDescription: Function; + setEnvironment: Function; + setPlatform: Function; + setSelectedscenario: Function; + setSelectedtrack: Function; + setScenarioType: Function; + setHardware: Function; + setSearchval: Function; + setTitleError: Function; + setSTypeError: Function; + setTrackError: Function; + setScenarioError: Function; +} +// vehicle list in track list +export interface GetVehicleListType { + findTrackById: { + description: string; + duration: string; + id: string; + name: string; + state: string; + trackType: string; + vehicles: [{ + brand: string; + country: string; + devices: [{ + components: [{ + environmentType: string; + id: string; + name: string; + status: string; + version: string; + }] + createdAt: string; + dmProtocol: string; + dmProtocolVersion: string; + gatewayId: string; + id: string; + modelType: string; + modifiedAt: string; + serialNumber: string; + status: string; + type: string; + }]; + status: string; + type: string; + updatedAt: string; + vin: string; + }] + } +} +// simulation data +export interface RawDataSimType { + data: { + simulationReadByQuery: { + content: [{ + brands: []; + createdBy: string; + description: string; + environment: string; + hardware: string; + id: string; + name: string; + noOfScenarios: number; + noOfVehicle: number; + platform: string; + scenarioType: string; + startDate: string; + status: string; + }]; + elements: number; + empty: boolean; + first: boolean; + last: boolean; + page: number; + pages: number; + size: number; + total: number; + } + } +} +// track data +export interface RawDataTrackType { + data: { + searchTrackByPattern: { + content: [{ + id: string; + name: string; + state: string; + trackType: string; + vehicles: [{ + country: string; + vin: string; + }] + }]; + elements: number; + empty: boolean; + first: boolean; + last: boolean; + page: number; + pages: number; + size: number; + total: number; + } + } +} + +export interface VehicleTypes { + brand: string, + country: string, + devices: [{}], + status: string, + type: string, + updatedAt: string, + vin: string, +} +export interface ComponentTypes { + name: string, + version: string, +} \ No newline at end of file diff --git a/developer-console-ui/package.json b/developer-console-ui/package.json new file mode 100644 index 0000000000000000000000000000000000000000..73d9898cb1df558b6c888c9538c003f3db15c043 --- /dev/null +++ b/developer-console-ui/package.json @@ -0,0 +1,60 @@ +{ + "private": true, + "scripts": { + "postinstall": "husky install", + "release": "semantic-release", + "release:dry": "semantic-release --dry-run --no-ci" + }, + "devDependencies": { + "auto-changelog": "^2.3.0", + "@commitlint/cli": "^16.2.4", + "@commitlint/config-conventional": "^16.2.4", + "@semantic-release/changelog": "^6.0.1", + "@semantic-release/git": "^10.0.1", + "@semantic-release/gitlab": "^9.2.0", + "husky": "^7.0.4", + "semantic-release": "^19.0.2" + }, + "commitlint": { + "extends": [ + "@commitlint/config-conventional" + ] + }, + "release": { + "branches": [ + "main" + ], + "tagFormat": "${version}", + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator", + [ + "@semantic-release/changelog", + { + "changelogFile": "CHANGELOG.md" + } + ], + [ + "@semantic-release/git", + { + "assets": [ + "CHANGELOG.md" + ], + "message": "release: ${nextRelease.version}\n\n${nextRelease.notes}" + } + ], + [ + "@semantic-release/gitlab", + { + "assets": [ + { + "path": "CHANGELOG.md" + } + ] + } + ] + ] + }, + "dependencies": { + } +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..8e3ec6bbeec81eb221638973433c7619efbc5cd7 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,124 @@ +version: "3.5" + +networks: + services: + name: services + driver: bridge + +services: + developer-console-ui: + image: developer-console-ui:1.0 + container_name: developer-console-ui + build: + context: . + dockerfile: developer-console-ui/Dockerfile + ports: + - "3000:3000" + environment: + APP_DCO_GATEWAY_SERVICE_URL: http://localhost:8080 + NEXT_PUBLIC_FILE: http://minio:9000/dco-scenario-library-service/scenario/fe7f4c94-c21a-4a4b-a3fc-c1b309b0f5f0/files/software_package.txt + restart: unless-stopped + + dco-gateway: + image: dco-gateway:1.0 + container_name: dco-gateway + build: + context: . + dockerfile: dco-gateway/Dockerfile.app + ports: + - "8080:8080" + environment: + APP_REST_PORT: 8080 + TRACK_MANAGEMENT_URL: tracks-management-service:8081 + SCENARIO_LIBRARY_URL: scenario-library-service:8082 + restart: unless-stopped + + tracks-management-service: + image: tracks-management-service:1.0 + container_name: tracks-management-service + build: + context: . + dockerfile: tracks-management-service/Dockerfile.app + ports: + - "8081:8081" + environment: + APP_REST_PORT: 8081 + APP_POSTGRES_HOST: postgres + APP_POSTGRES_PORT: 5432 + APP_POSTGRES_DATABASE: postgres + APP_POSTGRES_USERNAME: postgres + APP_POSTGRES_PASSWORD: postgres + SCENARIO-SERVICE_URL: scenario-library-service:8082 + restart: unless-stopped + depends_on: + - postgres + - pgadmin + + scenario-library-service: + image: scenario-library-service:1.0 + container_name: scenario-library-service + build: + context: . + dockerfile: scenario-library-service/Dockerfile.app + ports: + - "8082:8082" + env_file: + - minio/minio_keys.env + environment: + APP_REST_PORT: 8082 + APP_STORAGE_URL: http://minio:9000 + APP_STORAGE_BUCKET: dco-scenario-library-service + APP_POSTGRES_HOST: postgres + APP_POSTGRES_PORT: 5432 + APP_POSTGRES_DATABASE: postgres + APP_POSTGRES_USERNAME: postgres + APP_POSTGRES_PASSWORD: postgres + TRACK-SERVICE_URL: tracks-management-service:8081 + restart: unless-stopped + depends_on: + - postgres + - pgadmin + + minio: + image: minio:1.0 + build: + context: . + dockerfile: minio/Dockerfile.minio + container_name: minio + hostname: minio + user: 1000:1000 + ports: + - "9000:9000" + - "9001:9001" + environment: + MINIO_ROOT_USER: minioadmin + MINIO_ROOT_PASSWORD: minioadmin + MINIO_SERVER_URL: http://localhost:9000 + restart: unless-stopped + + postgres: + image: postgres:1.0 + build: + context: . + dockerfile: postgres/Dockerfile.database + container_name: postgres + ports: + - "5432:5432" + environment: + POSTGRES_DB: postgres + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + restart: unless-stopped + + pgadmin: + image: dpage/pgadmin4 + container_name: pgadmin + environment: + PGADMIN_DEFAULT_EMAIL: admin@default.com + PGADMIN_DEFAULT_PASSWORD: admin + ports: + - "5050:80" + depends_on: + - postgres + restart: unless-stopped + diff --git a/images/Access-Seccret-Key.png b/images/Access-Seccret-Key.png new file mode 100644 index 0000000000000000000000000000000000000000..abdb5653f77f554f71680065ed69a2091f992dc3 Binary files /dev/null and b/images/Access-Seccret-Key.png differ diff --git a/images/Create-Bucket.png b/images/Create-Bucket.png new file mode 100644 index 0000000000000000000000000000000000000000..8a68aa0b54e4638ba5ac223a2bc412e64e884769 Binary files /dev/null and b/images/Create-Bucket.png differ diff --git a/images/Minio-Login-Page.png b/images/Minio-Login-Page.png new file mode 100644 index 0000000000000000000000000000000000000000..8cfd9f86a577317ab1c7e86068f7a73b84142cb6 Binary files /dev/null and b/images/Minio-Login-Page.png differ diff --git a/images/SDV-DCO-Architecture.png b/images/SDV-DCO-Architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..f2f280e13615ac694eded2ecae0db80ff9ad90f4 Binary files /dev/null and b/images/SDV-DCO-Architecture.png differ diff --git a/images/SimulationProcessDiag.jpg b/images/SimulationProcessDiag.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a31c4fb68f6e7ee89d09a90a9c21c5c272e98cf4 Binary files /dev/null and b/images/SimulationProcessDiag.jpg differ diff --git a/images/add-server-config-1.png b/images/add-server-config-1.png new file mode 100644 index 0000000000000000000000000000000000000000..ad209c2e83461979e9dc60aa3988b8a426cecbe3 Binary files /dev/null and b/images/add-server-config-1.png differ diff --git a/images/add-server-config-2.png b/images/add-server-config-2.png new file mode 100644 index 0000000000000000000000000000000000000000..278354ad46e62f188dd844ee448e4e185e8e1c46 Binary files /dev/null and b/images/add-server-config-2.png differ diff --git a/images/added-local-server.png b/images/added-local-server.png new file mode 100644 index 0000000000000000000000000000000000000000..d2e9b4c779bcc19d664c5f1cf3e0a0a9d3bbc9ce Binary files /dev/null and b/images/added-local-server.png differ diff --git a/images/dco-gateway-playground.png b/images/dco-gateway-playground.png new file mode 100644 index 0000000000000000000000000000000000000000..240a495be972eb1afda1ac7db9588f998f83f763 Binary files /dev/null and b/images/dco-gateway-playground.png differ diff --git a/images/developer-console-ui.png b/images/developer-console-ui.png new file mode 100644 index 0000000000000000000000000000000000000000..b6a005a4c448b8417d3e6a07daba1353b4b98f10 Binary files /dev/null and b/images/developer-console-ui.png differ diff --git a/images/pdadmin-login-page.png b/images/pdadmin-login-page.png new file mode 100644 index 0000000000000000000000000000000000000000..e16e9c55cfeb22c512168b93680d6a4db2ba7436 Binary files /dev/null and b/images/pdadmin-login-page.png differ diff --git a/images/scenario-library-service-swagger.png b/images/scenario-library-service-swagger.png new file mode 100644 index 0000000000000000000000000000000000000000..5bd26adb35e06cadfa73c28aa0bdae90a3f438c4 Binary files /dev/null and b/images/scenario-library-service-swagger.png differ diff --git a/images/sdv-dco-logo.png b/images/sdv-dco-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..cc7241973b7ec3071e8de6ed4743f6793792e2c0 Binary files /dev/null and b/images/sdv-dco-logo.png differ diff --git a/images/tracks-management-service-swagger.png b/images/tracks-management-service-swagger.png new file mode 100644 index 0000000000000000000000000000000000000000..946a8dee482b83888fb37a8bed8f88a699b3d20c Binary files /dev/null and b/images/tracks-management-service-swagger.png differ diff --git a/minio/Dockerfile.minio b/minio/Dockerfile.minio new file mode 100644 index 0000000000000000000000000000000000000000..2cfa8c565a1b678821963c79e8660b4ba724188f --- /dev/null +++ b/minio/Dockerfile.minio @@ -0,0 +1,4 @@ +FROM minio/minio:latest AS app +RUN mkdir -p /data +RUN chown -R 1000:1000 /data +CMD ["minio", "server", "/data", "--address", ":9000", "--console-address", ":9001"] diff --git a/postgres/Dockerfile.database b/postgres/Dockerfile.database new file mode 100644 index 0000000000000000000000000000000000000000..d9b0ebe1fca9572979d45d56c94c512c5f833fa6 --- /dev/null +++ b/postgres/Dockerfile.database @@ -0,0 +1,2 @@ +FROM postgres:12-alpine AS db +COPY postgres/dco-init.sql /docker-entrypoint-initdb.d/dco-init.sql diff --git a/postgres/dco-init.sql b/postgres/dco-init.sql new file mode 100644 index 0000000000000000000000000000000000000000..69c38217a2e34230ab4a6526c710b149a3d659a4 --- /dev/null +++ b/postgres/dco-init.sql @@ -0,0 +1,17 @@ +/* scenario-library-service */ +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; +CREATE TABLE IF NOT EXISTS scenario (id uuid DEFAULT uuid_generate_v4 () not null, created_at timestamp, created_by varchar(255), description varchar(255), updated_at timestamp, updated_by varchar(255), name varchar(255), status varchar(255), type varchar(255), file_id uuid, primary key (id)); +CREATE TABLE IF NOT EXISTS file (id uuid DEFAULT uuid_generate_v4 () not null, checksum varchar(255), file_key varchar(255), path varchar(255), size varchar(255), updated_by varchar(255), updated_on timestamp, primary key (id)); +ALTER TABLE scenario ADD CONSTRAINT fk_track_id FOREIGN KEY (file_id) REFERENCES file; + +CREATE TABLE IF NOT EXISTS simulation (id uuid DEFAULT uuid_generate_v4 () not null, campaign_id uuid, created_at timestamp, description varchar(255), environment varchar(255), hardware varchar(255), platform varchar(255), scenario_type int4, created_by varchar(255), start_date timestamp, status varchar(255), name varchar(255), primary key (id)); +CREATE TABLE IF NOT EXISTS simulation_scenarios (simulation_id uuid DEFAULT uuid_generate_v4 () not null, scenarios uuid); +CREATE TABLE IF NOT EXISTS simulation_tracks (simulation_id uuid DEFAULT uuid_generate_v4 () not null, tracks uuid); +ALTER TABLE simulation_scenarios ADD CONSTRAINT fk_simulation_id FOREIGN KEY (simulation_id) REFERENCES simulation; +ALTER TABLE simulation_tracks ADD CONSTRAINT fk_simulation_id FOREIGN KEY (simulation_id) REFERENCES simulation; + +/* tracks-management-service */ +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; +CREATE TABLE IF NOT EXISTS track (id uuid DEFAULT uuid_generate_v4 () not null, created_at timestamp, description varchar(255), duration varchar(255), name varchar(255), state varchar(255), track_type varchar(255), primary key (id)); +CREATE TABLE IF NOT EXISTS vehicle (id uuid DEFAULT uuid_generate_v4 () not null, country varchar(255), vin varchar(255), track_id uuid, primary key (id)); +ALTER TABLE vehicle add constraint fk_track_id foreign key (track_id) references track; \ No newline at end of file diff --git a/scenario-library-service/.gitignore b/scenario-library-service/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..d5c46a85cb0d44005d74a079fab89ed4dc025e0f --- /dev/null +++ b/scenario-library-service/.gitignore @@ -0,0 +1,23 @@ +HELP.md +target/ +api/target/ +app/target/ +app-database/target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr \ No newline at end of file diff --git a/scenario-library-service/Dockerfile.app b/scenario-library-service/Dockerfile.app new file mode 100644 index 0000000000000000000000000000000000000000..469d8bdc4593c4daa570eaccef887182ff95bba2 --- /dev/null +++ b/scenario-library-service/Dockerfile.app @@ -0,0 +1,4 @@ +FROM amazoncorretto:17-al2-jdk AS app +COPY scenario-library-service/app/target/*.jar /app/app.jar +WORKDIR /app +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/scenario-library-service/api/openapi/openapi-scenario.yml b/scenario-library-service/api/openapi/openapi-scenario.yml new file mode 100644 index 0000000000000000000000000000000000000000..c6a6d0c6bdb928652f89b2ef284baf5f9a4f5709 --- /dev/null +++ b/scenario-library-service/api/openapi/openapi-scenario.yml @@ -0,0 +1,491 @@ +# TODO: adapt openapi specification itself and file name +openapi: 3.0.1 +info: + title: openapi-scenario + version: latest +servers: + - url: http://localhost:8080 +tags: + - name: Scenario + description: The endpoints for scenario interactions + - name: Simulation + description: The endpoints for simulation interactions +paths: + /api/scenario: + post: + tags: + - Scenario + summary: Create a Scenario + description: Create a Scenario to database + operationId: createScenario + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + scenario: + type: string + file: + type: string + format: binary + responses: + "201": + description: Created + content: + application/json: + schema: + $ref: "#/components/schemas/Scenario" + "400": + description: Bad Request + get: + tags: + - Scenario + summary: Read Scenario by query + description: Read Scenario by query and pageable from database + operationId: scenarioReadByQuery + parameters: + - name: query + in: query + required: false + description: >- + Comma separated list of `{field}{operation}{value}` where operation can be + `:` for equal, + `!` for not equal and + `~` for like operation + schema: + type: string + - name: search + in: query + required: false + description: Search value to query searchable fields against + schema: + type: string + - name: page + in: query + required: false + example: 0 + schema: + type: integer + format: int32 + - name: size + in: query + required: false + example: 15 + schema: + type: integer + format: int32 + - name: sort + in: query + required: false + example: + - name:asc + schema: + type: array + items: + type: string + example: name:asc + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/ScenarioPage" + "400": + description: Bad Request + put: + tags: + - Scenario + summary: Update Scenario by id + description: Update Scenario by id to database + operationId: scenarioUpdateById + parameters: + - name: id + in: query + description: The scenario id + required: true + schema: + type: string + format: uuid + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + scenario: + type: string + file: + type: string + format: binary + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/Scenario" + "400": + description: Bad Request + "404": + description: Not Found + delete: + tags: + - Scenario + summary: Delete Scenario by id + description: Delete Scenario by id from database + operationId: deleteScenarioById + parameters: + - name: id + in: query + description: The scenario id + required: true + schema: + type: string + format: uuid + responses: + "204": + description: No Content + "404": + description: Not Found + /api/scenario/search: + get: + tags: + - Scenario + summary: Search for scenario with given '%' pattern. Returns paginated list + description: Search for scenario with given '%' pattern. Returns paginated list + operationId: searchScenarioByPattern + parameters: + - name: scenarioPattern + in: query + required: true + schema: + type: string + - name: page + in: query + required: false + example: 0 + schema: + type: integer + format: int32 + - name: size + in: query + required: false + example: 15 + schema: + type: integer + format: int32 + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/ScenarioPage" + "404": + description: Not Found + /api/simulation: + post: + tags: + - Simulation + summary: Launch Simulation + description: Launch a Simulation + operationId: launchSimulation + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/SimulationInput" + required: true + responses: + "201": + description: Created + content: + application/json: + schema: + type: string + "400": + description: Bad Request + "404": + description: Not Found + get: + tags: + - Simulation + summary: Read Simulation by query + description: Read Simulation by query and pageable from database + operationId: simulationReadByQuery + parameters: + - name: query + in: query + required: false + description: >- + Comma separated list of `{field}{operation}{value}` where operation can be + `:` for equal, + `!` for not equal and + `~` for like operation + schema: + type: string + - name: search + in: query + required: false + description: Search value to query searchable fields against + schema: + type: string + - name: page + in: query + required: false + example: 0 + schema: + type: integer + format: int32 + - name: size + in: query + required: false + example: 15 + schema: + type: integer + format: int32 + - name: sort + in: query + required: false + example: + - name:asc + schema: + type: array + items: + type: string + example: name:asc + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/SimulationPage" + "400": + description: Bad Request + /api/simulation/track: + get: + tags: + - Simulation + summary: check track associated with simulation + description: check track associated with simulation + operationId: isTrackAssociatedWithSimulation + parameters: + - name: id + in: query + description: The track id + required: true + schema: + type: string + format: uuid + responses: + "200": + description: OK + content: + application/json: + schema: + type: boolean + "400": + description: Bad Request + "404": + description: Not Found +components: + schemas: + ScenarioInput: + type: object + properties: + name: + type: string + status: + type: string + enum: [ CREATED, ARCHIVED ] + type: + type: string + enum: [ MQTT, CAN ] + description: + type: string + createdBy: + type: string + lastModifiedBy: + type: string + description: The scenario data + FileData: + type: object + properties: + id: + type: string + format: uuid + path: + type: string + fileKey: + type: string + size: + type: string + checksum: + type: string + updatedBy: + type: string + updatedOn: + type: string + format: date-time + description: The scenario data + ScenarioPage: + type: object + properties: + content: + type: array + items: + $ref: '#/components/schemas/Scenario' + empty: + type: boolean + first: + type: boolean + last: + type: boolean + page: + type: integer + format: int32 + size: + type: integer + format: int32 + pages: + type: integer + format: int32 + elements: + type: integer + format: int32 + total: + type: integer + format: int64 + description: The Scenario page data + Scenario: + type: object + properties: + id: + type: string + format: uuid + name: + type: string + type: + type: string + status: + type: string + description: + type: string + createdAt: + type: string + format: date-time + createdBy: + type: string + lastModifiedAt: + type: string + format: date-time + lastModifiedBy: + type: string + file: + $ref: "#/components/schemas/FileData" + description: The scenario data + SimulationInput: + type: object + properties: + name: + type: string + environment: + type: string + platform: + type: string + scenarioType: + type: enum + $ref: "#/components/schemas/ScenarioType" + hardware: + type: string + description: + type: string + tracks: + type: array + items: + type: string + format: uuid + scenarios: + type: array + items: + type: string + format: uuid + createdBy: + type: string + description: launch simulation input + SimulationPage: + type: object + properties: + content: + type: array + items: + $ref: '#/components/schemas/Simulation' + empty: + type: boolean + first: + type: boolean + last: + type: boolean + page: + type: integer + format: int32 + size: + type: integer + format: int32 + pages: + type: integer + format: int32 + elements: + type: integer + format: int32 + total: + type: integer + format: int64 + description: The Scenario page data + Simulation: + type: object + properties: + id: + type: string + format: uuid + name: + type: string + status: + type: string + platform: + type: string + hardware: + type: string + environment: + type: string + scenarioType: + type: enum + $ref: "#/components/schemas/ScenarioType" + noOfScenarios: + type: integer + format: int32 + noOfVehicle: + type: integer + format: int32 + brands: + type: array + items: + type: string + createdBy: + type: string + startDate: + type: string + format: date-time + description: + type: string + description: Simulation Data + ScenarioType: + type: string + enum: + - Over-The-Air Service + - Vehicle Management + - Data Collection + - Remote Control diff --git a/scenario-library-service/api/pom.xml b/scenario-library-service/api/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..90f30ec297869e845f1b85760152abc82050632d --- /dev/null +++ b/scenario-library-service/api/pom.xml @@ -0,0 +1,83 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <artifactId>scenario-library-service</artifactId> + <groupId>com.tsystems.dco</groupId> + <version>latest</version> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>scenario-library-service-api</artifactId> + <packaging>jar</packaging> + + <properties> + <cyclonedx.skip>true</cyclonedx.skip> + <dependency-track.skip>true</dependency-track.skip> + </properties> + + <dependencies> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-web</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.springdoc</groupId> + <artifactId>springdoc-openapi-ui</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.springdoc</groupId> + <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> + <version>2.1.0</version> + </dependency> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.openapitools</groupId> + <artifactId>openapi-generator-maven-plugin</artifactId> + <executions> + <execution> + <id>openapi-scenario</id> + <goals> + <goal>generate</goal> + </goals> + <configuration> + <inputSpec>${project.basedir}/openapi/openapi-scenario.yml</inputSpec> + <generatorName>spring</generatorName> + <packageName>com.tsystems.dco</packageName> + <invokerPackage>com.tsystems.dco</invokerPackage> + <apiPackage>com.tsystems.dco.api</apiPackage> + <modelPackage>com.tsystems.dco.model</modelPackage> + <generateSupportingFiles>false</generateSupportingFiles> + <typeMappings> + <typeMapping>OffsetDateTime=Instant</typeMapping> + </typeMappings> + <importMappings> + <importMapping>java.time.OffsetDateTime=java.time.Instant</importMapping> + </importMappings> + <configOptions> + <useTags>true</useTags> + <interfaceOnly>true</interfaceOnly> + <useSpringBoot3>true</useSpringBoot3> + <serializableModel>true</serializableModel> + <skipDefaultInterface>true</skipDefaultInterface> + <hideGenerationTimestamp>true</hideGenerationTimestamp> + <openApiNullable>false</openApiNullable> + <additionalModelTypeAnnotations>@lombok.Builder @lombok.NoArgsConstructor @lombok.AllArgsConstructor</additionalModelTypeAnnotations> + </configOptions> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project> diff --git a/scenario-library-service/app-database/pom.xml b/scenario-library-service/app-database/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..49a896bfa89ce41f4388cf5988638961d1f4729a --- /dev/null +++ b/scenario-library-service/app-database/pom.xml @@ -0,0 +1,92 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <artifactId>scenario-library-service</artifactId> + <groupId>com.tsystems.dco</groupId> + <version>latest</version> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>scenario-library-service-app-database</artifactId> + <properties> + <sonar.coverage.exclusions> + **/AppDatabase.java + </sonar.coverage.exclusions> + </properties> + <dependencies> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-data-jpa</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-configuration-processor</artifactId> + <optional>true</optional> + </dependency> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + </dependency> + <dependency> + <groupId>org.liquibase</groupId> + <artifactId>liquibase-core</artifactId> + </dependency> + <dependency> + <groupId>org.postgresql</groupId> + <artifactId>postgresql</artifactId> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.module</groupId> + <artifactId>jackson-module-jaxb-annotations</artifactId> + </dependency> + <!-- test --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-test</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <resources> + <resource> + <directory>src/main/resources</directory> + <filtering>true</filtering> + </resource> + </resources> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <annotationProcessorPaths> + <path> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + <version>1.18.24</version> + </path> + <path> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-configuration-processor</artifactId> + <version>2.7.4</version> + </path> + </annotationProcessorPaths> + </configuration> + </plugin> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + <executions> + <execution> + <goals> + <goal>repackage</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project> diff --git a/scenario-library-service/app-database/src/main/java/dco/AppDatabase.java b/scenario-library-service/app-database/src/main/java/dco/AppDatabase.java new file mode 100644 index 0000000000000000000000000000000000000000..fb748d5b2291f5405d18ddddee845d55e62735b4 --- /dev/null +++ b/scenario-library-service/app-database/src/main/java/dco/AppDatabase.java @@ -0,0 +1,26 @@ +package dco; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; + +/** + * Entrypoint of application for database migrations. + */ +@Slf4j +@EnableConfigurationProperties +@SpringBootApplication +public class AppDatabase { + + /** + * Main application entrypoint. + * + * @param args command line arguments + */ + public static void main(String[] args) { + var ctx = SpringApplication.run(AppDatabase.class, args); + var code = SpringApplication.exit(ctx); + System.exit(code); + } +} diff --git a/scenario-library-service/app-database/src/main/java/dco/AppDatabaseProperties.java b/scenario-library-service/app-database/src/main/java/dco/AppDatabaseProperties.java new file mode 100644 index 0000000000000000000000000000000000000000..9cdbcddb66aeec112dd02479fc4a1cfd36b4b77a --- /dev/null +++ b/scenario-library-service/app-database/src/main/java/dco/AppDatabaseProperties.java @@ -0,0 +1,66 @@ +package dco; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; +import org.springframework.context.annotation.Configuration; + +/** + * Properties of application for database migrations. + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Configuration +@ConfigurationProperties(prefix = "app") +public class AppDatabaseProperties { + + /** + * The app name. + */ + private String name; + /** + * The app version. + */ + private String version; + /** + * The postgres properties. + */ + @NestedConfigurationProperty + private Postgres postgres; + + /** + * Properties of postgres. + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Postgres { + + /** + * The postgres host. + */ + private String host; + /** + * The postgres port. + */ + private Integer port; + /** + * The postgres database. + */ + private String database; + /** + * The postgres username. + */ + private String username; + /** + * The postgres password. + */ + private String password; + } +} diff --git a/scenario-library-service/app-database/src/main/resources/application.yml b/scenario-library-service/app-database/src/main/resources/application.yml new file mode 100644 index 0000000000000000000000000000000000000000..18b767fcfd29728c07ee673d836fe993f4e7afae --- /dev/null +++ b/scenario-library-service/app-database/src/main/resources/application.yml @@ -0,0 +1,22 @@ +app: + postgres: + host: localhost + port: 5432 + database: postgres + username: postgres + password: postgres +spring: + main: + web-application-type: none + datasource: + driver-class-name: org.postgresql.Driver + url: jdbc:postgresql://${app.postgres.host}:${app.postgres.port}/${app.postgres.database} + username: ${app.postgres.username} + password: ${app.postgres.password} + jpa: + database-platform: org.hibernate.dialect.PostgreSQLDialect + hibernate: + ddl-auto: validate + liquibase: + enabled: true + change-log: classpath:db/changelog.yml diff --git a/scenario-library-service/app-database/src/main/resources/db/changelog.yml b/scenario-library-service/app-database/src/main/resources/db/changelog.yml new file mode 100644 index 0000000000000000000000000000000000000000..36b09e11878914fa0138e15a58624be5b3fa97b8 --- /dev/null +++ b/scenario-library-service/app-database/src/main/resources/db/changelog.yml @@ -0,0 +1,4 @@ +databaseChangeLog: +- include: + file: changelog/v000-scenario-schema.sql + relativeToChangelogFile: true diff --git a/scenario-library-service/app-database/src/main/resources/db/changelog/v000-scenario-schema.sql b/scenario-library-service/app-database/src/main/resources/db/changelog/v000-scenario-schema.sql new file mode 100644 index 0000000000000000000000000000000000000000..5673dd254b8ea3ebc0926ae8c803d67866420ded --- /dev/null +++ b/scenario-library-service/app-database/src/main/resources/db/changelog/v000-scenario-schema.sql @@ -0,0 +1,10 @@ +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; +CREATE TABLE IF NOT EXISTS scenario (id uuid DEFAULT uuid_generate_v4 () not null, created_at timestamp, created_by varchar(255), description varchar(255), updated_at timestamp, updated_by varchar(255), name varchar(255), status varchar(255), type varchar(255), file_id uuid, primary key (id)); +CREATE TABLE IF NOT EXISTS file (id uuid DEFAULT uuid_generate_v4 () not null, checksum varchar(255), file_key varchar(255), path varchar(255), size varchar(255), updated_by varchar(255), updated_on timestamp, primary key (id)); +ALTER TABLE scenario ADD CONSTRAINT fk_track_id FOREIGN KEY (file_id) REFERENCES file; + +CREATE TABLE IF NOT EXISTS simulation (id uuid DEFAULT uuid_generate_v4 () not null, campaign_id uuid, created_at timestamp, description varchar(255), environment varchar(255), hardware varchar(255), platform varchar(255), scenario_type int4, created_by varchar(255), start_date timestamp, status varchar(255), name varchar(255), primary key (id)); +CREATE TABLE IF NOT EXISTS simulation_scenarios (simulation_id uuid DEFAULT uuid_generate_v4 () not null, scenarios uuid); +CREATE TABLE IF NOT EXISTS simulation_tracks (simulation_id uuid DEFAULT uuid_generate_v4 () not null, tracks uuid); +ALTER TABLE simulation_scenarios ADD CONSTRAINT fk_simulation_id FOREIGN KEY (simulation_id) REFERENCES simulation; +ALTER TABLE simulation_tracks ADD CONSTRAINT fk_simulation_id FOREIGN KEY (simulation_id) REFERENCES simulation; diff --git a/scenario-library-service/app/pom.xml b/scenario-library-service/app/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..912c3cf8eaf9ec5eff57ec513ea279612b9e87d6 --- /dev/null +++ b/scenario-library-service/app/pom.xml @@ -0,0 +1,166 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <artifactId>scenario-library-service</artifactId> + <groupId>com.tsystems.dco</groupId> + <version>latest</version> + </parent> + <modelVersion>4.0.0</modelVersion> + <artifactId>scenario-library-service-app</artifactId> + <version>latest</version> + <packaging>jar</packaging> + + <properties> + <cyclonedx.skip>true</cyclonedx.skip> + <dependency-track.skip>false</dependency-track.skip> + <sonar.coverage.exclusions> + **/App.java, + **/src/main/java/com/tsystems/dco/integration/**, + **/src/main/java/com/tsystems/dco/*/entity/**, + **/src/main/java/com/tsystems/dco/file/config/**, + **/src/main/java/com/tsystems/dco/AppProperties** + </sonar.coverage.exclusions> + </properties> + + <dependencies> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-web</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-configuration-processor</artifactId> + <optional>true</optional> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-actuator</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-validation</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-security</artifactId> + </dependency> + <dependency> + <groupId>org.mapstruct</groupId> + <artifactId>mapstruct</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-data-jpa</artifactId> + </dependency> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + </dependency> + <!-- CVE-2022-38752 --> + <dependency> + <groupId>org.yaml</groupId> + <artifactId>snakeyaml</artifactId> + <version>1.32</version> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>scenario-library-service-api</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.springdoc</groupId> + <artifactId>springdoc-openapi-ui</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-starter-openfeign</artifactId> + </dependency> + <dependency> + <groupId>org.postgresql</groupId> + <artifactId>postgresql</artifactId> + <scope>runtime</scope> + </dependency> + <!-- Minio storage / AWS S3 --> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>s3</artifactId> + </dependency> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + <version>32.0.0-jre</version> + </dependency> + <!-- test --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-test</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <resources> + <resource> + <directory>src/main/resources</directory> + <filtering>true</filtering> + </resource> + </resources> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <annotationProcessorPaths> + <path> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + <version>1.18.24</version> + </path> + <path> + <groupId>org.mapstruct</groupId> + <artifactId>mapstruct-processor</artifactId> + <version>1.4.2.Final</version> + </path> + <path> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-configuration-processor</artifactId> + <version>2.7.4</version> + </path> + </annotationProcessorPaths> + </configuration> + </plugin> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + <executions> + <execution> + <id>prepare-agent</id> + <goals> + <goal>prepare-agent</goal> + </goals> + </execution> + <execution> + <id>report</id> + <phase>verify</phase> + <goals> + <goal>report</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + <executions> + <execution> + <goals> + <goal>repackage</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project> diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/App.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/App.java new file mode 100644 index 0000000000000000000000000000000000000000..9ab514f95f1495d2577873c41f092884e85bb624 --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/App.java @@ -0,0 +1,51 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +/** + * Entrypoint of application. + */ +@EnableConfigurationProperties +@EnableJpaRepositories +@EnableJpaAuditing +@EnableFeignClients +@SpringBootApplication +public class App { + + /** + * Main application entrypoint. + * + * @param args command line arguments + */ + public static void main(String[] args) { + SpringApplication.run(App.class, args); + } +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/AppProperties.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/AppProperties.java new file mode 100644 index 0000000000000000000000000000000000000000..bae8c69c2c7b3340ff87e505850d6070a7183043 --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/AppProperties.java @@ -0,0 +1,142 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; +import org.springframework.context.annotation.Configuration; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Properties of application. + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Configuration +@ConfigurationProperties(prefix = "app") +public class AppProperties { + + /** + * The app name. + */ + private String name; + /** + * The app version. + */ + private String version; + /** + * The rest properties. + */ + @NestedConfigurationProperty + private Rest rest; + /** + * The probes properties. + */ + @NestedConfigurationProperty + private Probes probes; + /** + * The cors properties. + */ + @NestedConfigurationProperty + private Cors cors; + + /** + * The postgres properties. + */ + @NestedConfigurationProperty + private Postgres postgres; + + /** + * Properties of rest. + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Rest { + + private Integer port; + } + + /** + * Properties of probes. + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Probes { + + private Integer port; + } + + /** + * Properties of cors. + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Cors { + + private String headers; + private String origins; + } + + /** + * Properties of postgres. + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Postgres { + + /** + * The postgres host. + */ + private String host; + /** + * The postgres port. + */ + private Integer port; + /** + * The postgres database. + */ + private String database; + /** + * The postgres username. + */ + private String username; + /** + * The postgres password. + */ + private String password; + } +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/config/SecurityConfig.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/config/SecurityConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..4373062d69fcf92c589abf2ce8bda1cae74d015f --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/config/SecurityConfig.java @@ -0,0 +1,58 @@ +package com.tsystems.dco.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.web.SecurityFilterChain; + +import static org.springframework.security.config.Customizer.withDefaults; + +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + @Value("${app.username}") + private String username; + @Value("${app.password}") + private String password; + + @Bean + public SecurityFilterChain configure(HttpSecurity http) throws Exception { + return http.csrf(csrf -> csrf.disable()) + .authorizeRequests(auth -> auth.anyRequest().authenticated()) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .httpBasic(withDefaults()) + .build(); + } + + @Bean + public InMemoryUserDetailsManager userDetailsManager() { + + UserDetails user1 = User.withDefaultPasswordEncoder() + .username(username) + .password(password) + .roles("USER") + .build(); + + UserDetails user2 = User.withDefaultPasswordEncoder() + .username("dco") + .password("dco") + .roles("USER") + .build(); + + UserDetails admin = User.withDefaultPasswordEncoder() + .username("admin") + .password("password") + .roles("USER","ADMIN") + .build(); + + return new InMemoryUserDetailsManager(user1, user2, admin); + } + +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/config/WebConfig.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/config/WebConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..f1a8e9ad383b6ddbb926fdaacd1f12db93ee41c2 --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/config/WebConfig.java @@ -0,0 +1,48 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.config; + +import com.tsystems.dco.AppProperties; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * Configuration for MVC and cors. + */ +@Configuration +@RequiredArgsConstructor +public class WebConfig implements WebMvcConfigurer { + + private final AppProperties properties; + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedMethods("HEAD", "GET", "PUT", "POST", "DELETE", "PATCH", "OPTIONS") + .allowedOrigins(properties.getCors().getOrigins()) + .allowedHeaders(properties.getCors().getHeaders()); + } +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/exception/BaseException.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/exception/BaseException.java new file mode 100644 index 0000000000000000000000000000000000000000..b01682e7cf58cb17e6792a1b118b1eaf322d8751 --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/exception/BaseException.java @@ -0,0 +1,49 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.exception; + +import lombok.Getter; +import lombok.ToString; +import org.springframework.http.HttpStatus; + +/** + * Base exception that contains a http status code for {@link BaseExceptionHandler}. + */ +@Getter +@ToString +public class BaseException extends RuntimeException { + + private final HttpStatus status; + + /** + * Base exception constructor with status and message. + * + * @param status The http status that should occur when exception is thrown + * @param message The message that should occur when exception is thrown + */ + public BaseException(HttpStatus status, String message) { + super(message); + this.status = status; + } +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/exception/BaseExceptionHandler.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/exception/BaseExceptionHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..4f10e28b7f84d74f9fdfef1c8f024e282d4b307a --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/exception/BaseExceptionHandler.java @@ -0,0 +1,126 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.exception; + +import lombok.Generated; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.BindException; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Base exception handler that returns a generic map as response to user on exception. + */ +@Slf4j +@Generated +@RestControllerAdvice +public class BaseExceptionHandler { + + private static final String KEY_STATUS = "status"; + private static final String KEY_MESSAGE = "message"; + + /** + * Default exception handler. + * + * @param exception the exception with message + * @return the response with status internal server error + */ + @ExceptionHandler(Exception.class) + public ResponseEntity<Map<String, Object>> handleExceptions(Exception exception) { + log.warn("Exception occurred. {}", exception.getMessage()); + log.warn("Exception content.", exception); + var status = HttpStatus.INTERNAL_SERVER_ERROR; + var body = new HashMap<String, Object>(); + body.put(KEY_STATUS, status); + body.put(KEY_MESSAGE, exception.getMessage()); + return ResponseEntity.status(status).body(body); + } + + /** + * Validation exception handler. + * + * @param exception the exception with message + * @return the response with status bad request + */ + @ExceptionHandler({ + BindException.class, + MethodArgumentNotValidException.class + }) + public ResponseEntity<Map<String, Object>> handleValidationExceptions(Exception exception) { + log.warn("Validation exception occurred. {}", exception.getMessage()); + var status = HttpStatus.BAD_REQUEST; + var body = new HashMap<String, Object>(); + body.put(KEY_STATUS, status); + body.put(KEY_MESSAGE, exception instanceof BindException + ? ((BindException) exception).getBindingResult().getAllErrors().stream() + .map(error -> { + var field = error.getCode(); + if (error instanceof FieldError fieldError) { + field = fieldError.getField(); + } + return String.format("%s %s", field, error.getDefaultMessage()); + }) + .collect(Collectors.joining(", ")) + : exception.getMessage()); + return ResponseEntity.status(status).body(body); + } + + /** + * DataNotFoundException handler. + * + * @param exception the base exception with message + * @return the response with status of base exception + */ + @ExceptionHandler(DataNotFoundException.class) + public ResponseEntity<Map<String, Object>> handleDataNotFoundException(DataNotFoundException exception) { + log.warn("DataNotFound exception occurred. {}", exception.getMessage()); + var body = new HashMap<String, Object>(); + body.put(KEY_STATUS, exception.getStatus()); + body.put(KEY_MESSAGE, exception.getMessage()); + return ResponseEntity.status(exception.getStatus()).body(body); + } + + /** + * DataDeletionException handler. + * + * @param exception the base exception with message + * @return the response with status of base exception + */ + @ExceptionHandler(DataDeletionException.class) + public ResponseEntity<Map<String, Object>> handleDataDeletionException(DataDeletionException exception) { + log.warn("DataDeletion exception occurred. {}", exception.getMessage()); + var body = new HashMap<String, Object>(); + body.put(KEY_STATUS, exception.getStatus()); + body.put(KEY_MESSAGE, exception.getMessage()); + return ResponseEntity.status(exception.getStatus()).body(body); + } + +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/exception/DataDeletionException.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/exception/DataDeletionException.java new file mode 100644 index 0000000000000000000000000000000000000000..5c0f19974f78d5280240e5498763009f57cbb852 --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/exception/DataDeletionException.java @@ -0,0 +1,46 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.exception; + +import lombok.Getter; +import lombok.ToString; +import org.springframework.http.HttpStatus; + +@Getter +@ToString +public class DataDeletionException extends RuntimeException { + + private final HttpStatus status; + + /** + * DataDeletionException constructor with status and message. + * + * @param status The http status that should occur when exception is thrown + * @param message The message that should occur when exception is thrown + */ + public DataDeletionException(HttpStatus status, String message) { + super(message); + this.status = status; + } +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/exception/DataNotFoundException.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/exception/DataNotFoundException.java new file mode 100644 index 0000000000000000000000000000000000000000..720a96cfa068f6dd2adfdb1b690d9c997fa8060c --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/exception/DataNotFoundException.java @@ -0,0 +1,48 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.exception; + +import lombok.Getter; +import lombok.ToString; +import org.springframework.http.HttpStatus; + +/** + * DataNotFoundException that contains a http status code for {@link BaseExceptionHandler}. + */ +@Getter +@ToString +public class DataNotFoundException extends RuntimeException { + private final HttpStatus status; + + /** + * DataNotFoundException constructor with status and message. + * + * @param status The http status that should occur when exception is thrown + * @param message The message that should occur when exception is thrown + */ + public DataNotFoundException(HttpStatus status, String message) { + super(message); + this.status = status; + } +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/file/config/S3Config.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/file/config/S3Config.java new file mode 100644 index 0000000000000000000000000000000000000000..3f9321134a58628bb6aec147341514454f2e8aa5 --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/file/config/S3Config.java @@ -0,0 +1,60 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.file.config; + +import lombok.Data; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; +import java.net.URI; + +@Data +@Configuration +public class S3Config { + + @Value("${app.storage.bucket}") + private String bucketName; + + @Value("${app.storage.url}") + private String minioEndpoint; + + @Value("${app.storage.access.key}") + private String minioAccessKey; + + @Value("${app.storage.secret.key}") + private String minioSecretKey; + + + @Bean + public S3Client s3Client() { + return S3Client.builder() + .endpointOverride(URI.create(minioEndpoint)) + .credentialsProvider(() -> AwsBasicCredentials.create(minioAccessKey, minioSecretKey)) + .region(Region.AWS_GLOBAL) + .build(); + } +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/file/entity/FileEntity.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/file/entity/FileEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..79e311e2cdf509f0a82a73581e1512feaf07d74c --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/file/entity/FileEntity.java @@ -0,0 +1,70 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.file.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; +import jakarta.persistence.*; +import java.time.Instant; +import java.util.UUID; + +@Builder(toBuilder = true) +@Data +@NoArgsConstructor +@AllArgsConstructor +@EntityListeners({ + AuditingEntityListener.class +}) +@Entity(name = "file") +public class FileEntity { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "id") + private UUID id; + + @Column(name = "path") + private String path; + + @Column(name = "size") + private String size; + + @Column(name = "checksum") + private String checksum; + + @Column(name = "updatedBy") + private String updatedBy; + + @Column(name = "file_key") + private String fileKey; + + @LastModifiedDate + @Column(name = "updated_on") + private Instant updatedOn; + +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/file/service/FileStorageService.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/file/service/FileStorageService.java new file mode 100644 index 0000000000000000000000000000000000000000..f5efe5faba87c6464854091be3c5c8ac4ec3a9f0 --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/file/service/FileStorageService.java @@ -0,0 +1,35 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.file.service; + +import com.tsystems.dco.model.FileData; +import org.springframework.web.multipart.MultipartFile; +import java.util.UUID; + +public interface FileStorageService { + + FileData uploadFile(UUID scenarioId, MultipartFile file, String updatedBy); + + void deleteFile(String key); +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/file/service/FileStorageServiceImpl.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/file/service/FileStorageServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..cad21d5872fc370b8acba637f3aff62ae8fa9930 --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/file/service/FileStorageServiceImpl.java @@ -0,0 +1,142 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.file.service; + +import com.google.common.io.BaseEncoding; +import com.tsystems.dco.file.config.S3Config; +import com.tsystems.dco.model.FileData; +import com.tsystems.dco.util.FileUtil; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.apache.tomcat.util.http.fileupload.FileUploadException; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; +import software.amazon.awssdk.core.checksums.Md5Checksum; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.S3Utilities; +import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; +import software.amazon.awssdk.services.s3.model.GetUrlRequest; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; + +import java.net.URI; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.UUID; +import java.util.zip.CheckedInputStream; + +@Service +@Slf4j +@RequiredArgsConstructor +public class FileStorageServiceImpl implements FileStorageService { + + private final S3Config s3Config; + private final S3Client s3Client; + + /** + * @param scenarioId + * @param file + * @param updatedBy + * @return FileData + */ + @SneakyThrows + @Override + public FileData uploadFile(UUID scenarioId, MultipartFile file, String updatedBy) { + String fileName = file.getOriginalFilename(); + if(!FileUtil.validateFilename(fileName)) { + throw new FileUploadException("File name is not valid"); + } + + long fileSize = file.getSize(); + String objectName = createObjectNameForFile(scenarioId, fileName); + log.info("S3 object name : {}", objectName); + if(objectName.getBytes().length > 1024) { + throw new FileUploadException("File name length is too long for storing it in S3"); + } + var checksum = new Md5Checksum(); + var checkedInputStream = new CheckedInputStream(file.getInputStream(), checksum); + + s3Client.putObject(PutObjectRequest.builder() + .bucket(s3Config.getBucketName()) + .key(objectName) + .build(), + RequestBody.fromInputStream(checkedInputStream, fileSize)); + + String filePath = getFilePath(objectName); + log.info("Uploaded file to {}", filePath); + + String checksumStr = BaseEncoding.base16().encode(checksum.getChecksumBytes()); + return FileData + .builder() + .path(filePath) + .fileKey(objectName) + .checksum(checksumStr) + .size(String.valueOf(fileSize)) + .updatedBy(updatedBy) + .updatedOn(Instant.now()) + .build(); + } + + + /** + * @param scenarioId + * @param fileName + * @return String + */ + private String createObjectNameForFile(UUID scenarioId, String fileName) { + var builder = new StringBuilder(); + builder.append("scenario/").append(scenarioId).append("/files/").append(fileName); + return builder.toString(); + } + + + /** + * @param objectName + * @return String + */ + private String getFilePath(String objectName) { + var s3Utilities = S3Utilities.builder() + .region(Region.AWS_GLOBAL) + .build(); + var fileUrl = s3Utilities.getUrl(GetUrlRequest.builder() + .endpoint(URI.create(s3Config.getMinioEndpoint())) + .bucket(s3Config.getBucketName()) + .key(objectName) + .build()); + return URLDecoder.decode(fileUrl.toString(), StandardCharsets.UTF_8); + } + + /** + * @param key + */ + @Override + public void deleteFile(String key) { + DeleteObjectRequest del = DeleteObjectRequest.builder() + .bucket(s3Config.getBucketName()).key(key).build(); + s3Client.deleteObject(del); + } +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/integration/Campaign.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/integration/Campaign.java new file mode 100644 index 0000000000000000000000000000000000000000..d2565883bacae5b504367243f4701e187db3486e --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/integration/Campaign.java @@ -0,0 +1,38 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.integration; + +import lombok.Builder; +import lombok.Data; + +import java.util.UUID; + +@Data +@Builder +public class Campaign { + + private UUID id; + private String status; + +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/integration/CampaignRequest.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/integration/CampaignRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..bc1f87097f9580ba2f56ff5adc1f1285e4fd8469 --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/integration/CampaignRequest.java @@ -0,0 +1,40 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.integration; + +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +public class CampaignRequest { + + String name; + String startDate; + String endDate; + Boolean sequential; + List<String> vehicles; +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/integration/FeignClientConfiguration.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/integration/FeignClientConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..5ac8e04684dd6a6a03286f9906d474518bc0d6b1 --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/integration/FeignClientConfiguration.java @@ -0,0 +1,44 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + + +package com.tsystems.dco.integration; + +import feign.auth.BasicAuthRequestInterceptor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class FeignClientConfiguration { + + @Value("${app.username}") + private String username; + @Value("${app.password}") + private String password; + + @Bean + public BasicAuthRequestInterceptor basicAuthRequestInterceptor() { + return new BasicAuthRequestInterceptor(username, password); + } +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/integration/Track.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/integration/Track.java new file mode 100644 index 0000000000000000000000000000000000000000..1fb8ef49653710ccafe5828f533beef031d6832b --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/integration/Track.java @@ -0,0 +1,37 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.integration; + +import lombok.Data; + +import java.util.List; +import java.util.UUID; + +@Data +public class Track { + + private UUID id; + private String name; + private List<VehicleResponse> vehicles; +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/integration/TrackRepositoryApiClient.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/integration/TrackRepositoryApiClient.java new file mode 100644 index 0000000000000000000000000000000000000000..07f18c1ad4e1ee04afe739f0eae8943484b4011f --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/integration/TrackRepositoryApiClient.java @@ -0,0 +1,44 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.integration; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.List; +import java.util.UUID; + +@FeignClient(name = "TrackRepositoryApiClient", + url = "${track-service.url}", + configuration = FeignClientConfiguration.class) +public interface TrackRepositoryApiClient { + + @GetMapping(value = "/api/track/list", produces = {"application/json"}) + ResponseEntity<List<Track>> findTrackByIds(@RequestParam(value = "trackIds") List<UUID> trackIds); + + @GetMapping(value = "/api/track/validate", produces = {"application/json"}) + ResponseEntity<Boolean> isTracksExists(@RequestParam(value = "trackIds") List<UUID> trackIds); +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/integration/VehicleResponse.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/integration/VehicleResponse.java new file mode 100644 index 0000000000000000000000000000000000000000..aa758e26406672947bb86aabe1e5b7755de7f254 --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/integration/VehicleResponse.java @@ -0,0 +1,35 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.integration; + +import lombok.Data; + +@Data +public class VehicleResponse { + + private String vin; + private String country; + private String model; + private String brand; +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/mapper/ScenarioMapper.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/mapper/ScenarioMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..650b208394cce73837f3e9903a365fe987c23863 --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/mapper/ScenarioMapper.java @@ -0,0 +1,48 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.mapper; + +import com.tsystems.dco.file.entity.FileEntity; +import com.tsystems.dco.model.FileData; +import com.tsystems.dco.model.Scenario; +import com.tsystems.dco.model.ScenarioInput; +import com.tsystems.dco.scenario.entity.ScenarioEntity; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; +import java.util.List; + +@Mapper +public interface ScenarioMapper { + + ScenarioMapper INSTANCE = Mappers.getMapper(ScenarioMapper.class); + + ScenarioEntity toEntity(ScenarioInput scenarioInput); + + List<Scenario> toModel(List<ScenarioEntity> scenarioEntities); + + Scenario toModel(ScenarioEntity scenarioEntity); + + FileEntity toEntity(FileData fileData); + +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/mapper/SimulationMapper.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/mapper/SimulationMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..3f199a13ba2d232e9fbb59fbb3ccc371ff3dba5b --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/mapper/SimulationMapper.java @@ -0,0 +1,43 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.mapper; + + +import com.tsystems.dco.model.Simulation; +import com.tsystems.dco.model.SimulationInput; +import com.tsystems.dco.simulation.entity.SimulationEntity; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; +import java.util.List; + +@Mapper +public interface SimulationMapper { + + SimulationMapper INSTANCE = Mappers.getMapper(SimulationMapper.class); + + SimulationEntity toEntity(SimulationInput simulationInput); + + List<Simulation> toModel(List<SimulationEntity> simulationEntities); + +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/scenario/controller/ScenarioController.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/scenario/controller/ScenarioController.java new file mode 100644 index 0000000000000000000000000000000000000000..a67a46a8d3e1978593b16ab7be51a885ed9edb2e --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/scenario/controller/ScenarioController.java @@ -0,0 +1,137 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.scenario.controller; + +import com.tsystems.dco.model.ScenarioPage; +import com.tsystems.dco.scenario.service.ScenarioService; +import com.tsystems.dco.api.ScenarioApi; +import com.tsystems.dco.model.Scenario; +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; +import java.util.UUID; + +@RestController +@RequiredArgsConstructor +public class ScenarioController implements ScenarioApi { + + private static final Logger LOGGER = LoggerFactory.getLogger(ScenarioController.class); + + private final ScenarioService scenarioService; + + /** + * POST /api/scenario : Create a Scenario + * Create a Scenario to database + * + * @param scenario (optional) + * @param file (optional) + * @return Created (status code 201) + * or Bad Request (status code 400) + */ + @Override + public ResponseEntity<Scenario> createScenario(String scenario, MultipartFile file) { + LOGGER.info("Creating Scenario - {}", scenario); + return ResponseEntity + .status(HttpStatus.CREATED) + .body(scenarioService.createScenario(scenario, file)); + } + + /** + * DELETE /api/scenario : Delete Scenario by id + * Delete Scenario by id from database + * + * @param id The scenario id (required) + * @return No Content (status code 204) + * or Not Found (status code 404) + */ + @Override + public ResponseEntity<Void> deleteScenarioById(UUID id) { + LOGGER.info("Deleting scenario id - {}", id); + scenarioService.deleteScenarioById(id); + return ResponseEntity + .status(HttpStatus.NO_CONTENT) + .build(); + } + + /** + * GET /api/scenario : Read Scenario by query + * Read Scenario by query and pageable from database + * + * @param query Comma separated list of `{field}{operation}{value}` where operation can be `:` for equal, `!` for not equal and `~` for like operation (optional) + * @param search Search value to query searchable fields against (optional) + * @param page (optional) + * @param size (optional) + * @param sort (optional) + * @return OK (status code 200) + * or Bad Request (status code 400) + */ + @Override + public ResponseEntity<ScenarioPage> scenarioReadByQuery(String query, String search, Integer page, Integer size, List<String> sort) { + return ResponseEntity + .status(HttpStatus.OK) + .body(scenarioService.readScenarioByQuery(query, search, page, size, sort)); + } + + /** + * PUT /api/scenario : Update Scenario by id + * Update Scenario by id to database + * + * @param id The scenario id (required) + * @param scenario (optional) + * @param file (optional) + * @return OK (status code 200) + * or Bad Request (status code 400) + * or Not Found (status code 404) + */ + @Override + public ResponseEntity<Scenario> scenarioUpdateById(UUID id, String scenario, MultipartFile file) { + return ResponseEntity + .status(HttpStatus.OK) + .body(scenarioService.scenarioUpdateById(id, scenario, file)); + } + + /** + * GET /api/scenario/search : Search for scenario with given '%' pattern. Returns paginated list + * Search for scenario with given '%' pattern. Returns paginated list + * + * @param scenarioPattern (required) + * @param page (optional) + * @param size (optional) + * @return OK (status code 200) + * or Not Found (status code 404) + */ + @Override + public ResponseEntity<ScenarioPage> searchScenarioByPattern(String scenarioPattern, Integer page, Integer size) { + LOGGER.info("searching scenario by pattern : {}", scenarioPattern); + return ResponseEntity + .status(HttpStatus.OK) + .body(scenarioService.searchScenarioByPattern(scenarioPattern, page, size)); + } +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/scenario/entity/ScenarioEntity.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/scenario/entity/ScenarioEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..f4d909cfa89b6813db7760cf4a6b6718b87affec --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/scenario/entity/ScenarioEntity.java @@ -0,0 +1,82 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.scenario.entity; + +import com.tsystems.dco.file.entity.FileEntity; +import lombok.*; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; + +import jakarta.persistence.*; +import java.time.Instant; +import java.util.UUID; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@EntityListeners({ + AuditingEntityListener.class +}) +@Entity(name = "scenario") +public class ScenarioEntity { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "id") + private UUID id; + + @Column(name = "name") + private String name; + + @Column(name = "status") + private String status; + + @Column(name = "type") + @EqualsAndHashCode.Include + private String type; + + @Column(name = "description") + private String description; + + @CreatedDate + @Column(name = "created_at") + private Instant createdAt; + + @Column(name = "created_by") + private String createdBy; + + @LastModifiedDate + @Column(name = "updated_at") + private Instant lastModifiedAt; + + @Column(name = "updated_by") + private String lastModifiedBy; + + @OneToOne(cascade = CascadeType.ALL) + @JoinColumn(name = "file_id", referencedColumnName = "id") + private FileEntity file; + +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/scenario/repository/ScenarioRepository.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/scenario/repository/ScenarioRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..40d9475992813e08730f2d4a8a638048ebd8b179 --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/scenario/repository/ScenarioRepository.java @@ -0,0 +1,40 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.scenario.repository; + +import com.tsystems.dco.scenario.entity.ScenarioEntity; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; +import java.util.UUID; + +@Repository +public interface ScenarioRepository extends JpaRepository<ScenarioEntity, UUID>, JpaSpecificationExecutor<ScenarioEntity> { + + @Query(value = "SELECT * FROM scenario where LOWER(name) like LOWER(?1) and status = 'CREATED'", nativeQuery = true) + Page<ScenarioEntity> findScenarioByLike(String scenarioPattern, Pageable pageable); +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/scenario/service/ScenarioQuery.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/scenario/service/ScenarioQuery.java new file mode 100644 index 0000000000000000000000000000000000000000..61921ed56f6c84aafe1d0444afe496b3904b9d9e --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/scenario/service/ScenarioQuery.java @@ -0,0 +1,97 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.scenario.service; + +import com.tsystems.dco.exception.BaseException; +import com.tsystems.dco.scenario.entity.ScenarioEntity; +import com.tsystems.dco.util.Operation; +import com.tsystems.dco.util.QueryUtil; +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.http.HttpStatus; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +@Getter +public class ScenarioQuery { + private final List<Query> queries = new ArrayList<>(); + + public ScenarioQuery(String query) { + var matcher = QueryUtil.QUERY_PATTERN.matcher(query + ","); + while (matcher.find()) { + queries.add(new Query( + matcher.group(1), + Operation.from(matcher.group(2).charAt(0)), + matcher.group(3)) + ); + } + } + + public ScenarioQuery isValid() { + if (queries.isEmpty()) { + throw new BaseException(HttpStatus.BAD_REQUEST, "The query does not match the valid pattern."); + } + return this; + } + + public Specification<ScenarioEntity> toSpecification() { + if (queries.isEmpty()) { + return null; + } + Specification<ScenarioEntity> result = queries.get(0); + for (var i = 1; i < queries.size(); i++) { + result = Specification.where(result).and(queries.get(i)); + } + return result; + } + + @Builder + @RequiredArgsConstructor + public static class Query implements Serializable, Specification<ScenarioEntity> { + private final String field; + private final Operation operation; + private final String value; + + @Override + public Predicate toPredicate( + Root<ScenarioEntity> root, + CriteriaQuery<?> query, + CriteriaBuilder builder + ) { + return switch (operation) { + case EQUAL -> builder.equal(root.get(field), value); + case NOT_EQUAL -> builder.notEqual(root.get(field), value); + case LIKE -> builder.like(root.get(field), "%" + value + "%"); + }; + } + } +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/scenario/service/ScenarioQueryUtil.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/scenario/service/ScenarioQueryUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..12ad0ddf1d06a03999a603103f9e3dd801c01af7 --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/scenario/service/ScenarioQueryUtil.java @@ -0,0 +1,61 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.scenario.service; + +import com.tsystems.dco.scenario.entity.ScenarioEntity; +import org.springframework.data.jpa.domain.Specification; +import com.tsystems.dco.util.Operation; +import java.util.Optional; + +public class ScenarioQueryUtil { + + private ScenarioQueryUtil() { + } + + /** + * @param query + * @param search + * @return Specification + */ + public static Specification<ScenarioEntity> getScenarioQuerySpecification(String query, String search) { + return Optional.ofNullable(search) + .map(s -> Specification + .where(ScenarioQuery.Query.builder() + .field("name").operation(Operation.LIKE).value(s) + .build()) + .or(ScenarioQuery.Query.builder() + .field("status").operation(Operation.LIKE).value(s) + .build()) + .or(ScenarioQuery.Query.builder() + .field("type").operation(Operation.LIKE).value(s) + .build()) + .or(ScenarioQuery.Query.builder() + .field("createdBy").operation(Operation.LIKE).value(s) + .build()) + .or(ScenarioQuery.Query.builder() + .field("lastModifiedBy").operation(Operation.LIKE).value(s) + .build())) + .orElse(new ScenarioQuery(query).toSpecification()); + } +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/scenario/service/ScenarioService.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/scenario/service/ScenarioService.java new file mode 100644 index 0000000000000000000000000000000000000000..053d4b2e1f8bfaa46ed9dd53ef8a8e8a862d6f73 --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/scenario/service/ScenarioService.java @@ -0,0 +1,44 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.scenario.service; + +import com.tsystems.dco.model.Scenario; +import com.tsystems.dco.model.ScenarioPage; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; +import java.util.UUID; + +public interface ScenarioService { + + Scenario createScenario(String scenarioInput, MultipartFile file); + + void deleteScenarioById(UUID id); + + ScenarioPage readScenarioByQuery(String query, String search, Integer page, Integer size, List<String> sort); + + Scenario scenarioUpdateById(UUID id, String scenarioInput, MultipartFile file); + + ScenarioPage searchScenarioByPattern(String scenarioPattern, Integer page, Integer size); +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/scenario/service/ScenarioServiceImpl.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/scenario/service/ScenarioServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..173bc6eb23c4b276f75d3489f40815d18197e39d --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/scenario/service/ScenarioServiceImpl.java @@ -0,0 +1,241 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.scenario.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.tsystems.dco.exception.DataDeletionException; +import com.tsystems.dco.exception.DataNotFoundException; +import com.tsystems.dco.file.entity.FileEntity; +import com.tsystems.dco.file.service.FileStorageService; +import com.tsystems.dco.mapper.ScenarioMapper; +import com.tsystems.dco.model.FileData; +import com.tsystems.dco.model.Scenario; +import com.tsystems.dco.model.ScenarioInput; +import com.tsystems.dco.model.ScenarioPage; +import com.tsystems.dco.scenario.entity.ScenarioEntity; +import com.tsystems.dco.scenario.repository.ScenarioRepository; +import com.tsystems.dco.simulation.entity.SimulationEntity; +import com.tsystems.dco.simulation.repository.SimulationRepository; +import com.tsystems.dco.util.QueryUtil; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import jakarta.transaction.Transactional; +import java.time.Instant; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class ScenarioServiceImpl implements ScenarioService { + + private static final Logger LOGGER = LoggerFactory.getLogger(ScenarioServiceImpl.class); + private static final String ERROR_NOT_FOUND_ID = "Scenario with id %s not found."; + private static final Sort.Order ORDER_DEFAULT = Sort.Order.asc("createdAt"); + private static final Integer PAGEABLE_DEFAULT_PAGE = 0; + private static final Integer PAGEABLE_DEFAULT_SIZE = 15; + private final ScenarioRepository scenarioRepository; + private final FileStorageService fileStorageService; + private final SimulationRepository simulationRepository; + + + /** + * @param scenarioInput + * @param file + * @return Scenario + */ + @SneakyThrows + @Transactional + @Override + public Scenario createScenario(String scenarioInput, MultipartFile file) { + var mapper = new ObjectMapper(); + ScenarioInput scenario = mapper.readValue(scenarioInput, ScenarioInput.class); + var scenarioEntity = ScenarioMapper.INSTANCE.toEntity(scenario); + scenarioEntity = scenarioRepository.save(scenarioEntity); + //file upload + FileData fileData = fileStorageService.uploadFile(scenarioEntity.getId(), file, scenarioEntity.getCreatedBy()); + attachFile(scenarioEntity, fileData); + return ScenarioMapper.INSTANCE.toModel(scenarioEntity); + } + + + /** + * @param scenarioEntity + * @param fileData + */ + private void attachFile(ScenarioEntity scenarioEntity, FileData fileData) { + FileEntity fileEntity = ScenarioMapper.INSTANCE.toEntity(fileData); + scenarioEntity.setFile(fileEntity); + scenarioRepository.save(scenarioEntity); + LOGGER.info("Attached file to scenario : {}", scenarioEntity.getName()); + } + + + /** + * @param id + */ + @Transactional + @Override + public void deleteScenarioById(UUID id) { + //update scenario status to archived + final var actual = scenarioRepository.findById(id) + .orElseThrow(() -> new DataNotFoundException(HttpStatus.NOT_FOUND, String.format(ERROR_NOT_FOUND_ID, id))); + //check, if associated with simulation + boolean isScenarioAssociated = isScenarioAssociatedWithSimulation(actual.getId()); + if (isScenarioAssociated) { + LOGGER.info("Deleting scenario : {}", actual.getId()); + actual.setStatus(ScenarioInput.StatusEnum.ARCHIVED.getValue()); + } else { + LOGGER.error("Scenario id : {}, can't be deleted as it has an association with simulation", actual.getId()); + throw new DataDeletionException(HttpStatus.BAD_REQUEST, "Scenario can't be deleted as it has an association with simulation"); + } + + } + + /** + * @param query + * @param search + * @param page + * @param size + * @param sort + * @return ScenarioPage + */ + @Override + public ScenarioPage readScenarioByQuery(String query, String search, Integer page, Integer size, List<String> sort) { + var pageable = QueryUtil.getPageRequest(page, size, sort); + var specs = ScenarioQueryUtil.getScenarioQuerySpecification(query, search); + var queried = scenarioRepository.findAll(specs, pageable); + return new ScenarioPage() + .empty(queried.isEmpty()) + .first(queried.isFirst()) + .last(queried.isLast()) + .page(queried.getNumber()) + .size(queried.getSize()) + .pages(queried.getTotalPages()) + .elements(queried.getNumberOfElements()) + .total(queried.getTotalElements()) + .content(ScenarioMapper.INSTANCE.toModel(queried.getContent())); + } + + + /** + * @param id + * @param scenarioInput + * @param file + * @return Scenario + */ + @SneakyThrows + @Transactional + @Override + public Scenario scenarioUpdateById(UUID id, String scenarioInput, MultipartFile file) { + var mapper = new ObjectMapper(); + ScenarioInput scenario = mapper.readValue(scenarioInput, ScenarioInput.class); + final var actualScenario = scenarioRepository.findById(id) + .orElseThrow(() -> new DataNotFoundException(HttpStatus.NOT_FOUND, String.format(ERROR_NOT_FOUND_ID, id))); + //update file + var fileEntity = actualScenario.getFile(); + if (file != null && !file.isEmpty()) { + FileData fileData = updateFile(id, fileEntity.getFileKey(), file, scenario.getLastModifiedBy()); + fileEntity.setUpdatedBy(fileData.getUpdatedBy()); + fileEntity.setFileKey(fileData.getFileKey()); + fileEntity.setChecksum(fileData.getChecksum()); + fileEntity.setSize(fileData.getSize()); + fileEntity.setPath(fileData.getPath()); + fileEntity.setUpdatedOn(fileData.getUpdatedOn()); + } + actualScenario.setName(scenario.getName()); + actualScenario.setType(scenario.getType().getValue()); + actualScenario.setDescription(scenario.getDescription()); + actualScenario.setLastModifiedBy(scenario.getLastModifiedBy()); + actualScenario.setLastModifiedAt(Instant.now()); + actualScenario.setFile(fileEntity); + final var updatedScenario = scenarioRepository.save(actualScenario); + return ScenarioMapper.INSTANCE.toModel(updatedScenario); + } + + + /** + * @param scenarioPattern + * @param page + * @param size + * @return ScenarioPage + */ + @Override + public ScenarioPage searchScenarioByPattern(String scenarioPattern, Integer page, Integer size) { + var pageable = PageRequest.of( + Optional.ofNullable(page).orElse(PAGEABLE_DEFAULT_PAGE), + Optional.ofNullable(size).orElse(PAGEABLE_DEFAULT_SIZE) + ); + Page<ScenarioEntity> scenarioEntities = scenarioRepository.findScenarioByLike(scenarioPattern + "%", pageable); + LOGGER.info("scenario search list - {}", scenarioEntities.getTotalElements()); + List<Scenario> scenarios = ScenarioMapper.INSTANCE.toModel(scenarioEntities.getContent()); + + return ScenarioPage.builder() + .content(scenarios) + .first(scenarioEntities.isFirst()) + .empty(scenarioEntities.isEmpty()) + .last(scenarioEntities.isLast()) + .page(scenarioEntities.getPageable().getPageNumber()) + .size(scenarioEntities.getPageable().getPageSize()) + .elements(scenarioEntities.getSize()) + .total(scenarioEntities.getTotalElements()) + .pages(scenarioEntities.getTotalPages()) + .build(); + } + + /** + * @param scenarioId + * @param key + * @param file + * @param modifiedBy + * @return FileData + */ + private FileData updateFile(UUID scenarioId, String key, MultipartFile file, String modifiedBy) { + fileStorageService.deleteFile(key); + return fileStorageService.uploadFile(scenarioId, file, modifiedBy); + } + + + /** + * @param scenarioId + * @return boolean + */ + private boolean isScenarioAssociatedWithSimulation(UUID scenarioId) { + List<SimulationEntity> simulationEntities = simulationRepository.findAll(); + List<SimulationEntity> matchedData = simulationEntities.stream().filter(sim -> sim.getScenarios().stream() + .anyMatch(t -> t.equals(scenarioId))).collect(Collectors.toList()); + return matchedData.isEmpty(); + } + +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/simulation/controller/SimulationController.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/simulation/controller/SimulationController.java new file mode 100644 index 0000000000000000000000000000000000000000..10df3f9034a8b506a2fff4620f0680bb3d5f9228 --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/simulation/controller/SimulationController.java @@ -0,0 +1,100 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.simulation.controller; + +import com.tsystems.dco.api.SimulationApi; +import com.tsystems.dco.model.SimulationInput; +import com.tsystems.dco.model.SimulationPage; +import com.tsystems.dco.simulation.service.SimulationService; +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.UUID; + +@RestController +@RequiredArgsConstructor +public class SimulationController implements SimulationApi { + + private static final Logger LOGGER = LoggerFactory.getLogger(SimulationController.class); + private final SimulationService simulationService; + + /** + * POST /api/simulation/launch : Launch Simulation + * Launch a Simulation + * + * @param simulationInput (required) + * @return OK (status code 200) + * or Bad Request (status code 400) + * or Not Found (status code 404) + */ + @Override + public ResponseEntity<String> launchSimulation(SimulationInput simulationInput) { + LOGGER.info("Launching Simulation - {}", simulationInput); + return ResponseEntity + .status(HttpStatus.CREATED) + .body(simulationService.launchSimulation(simulationInput)); + } + + /** + * GET /api/simulation : Read Simulation by query + * Read Simulation by query and pageable from database + * + * @param query Comma separated list of `{field}{operation}{value}` where operation can be `:` for equal, `!` for not equal and `~` for like operation (optional) + * @param search Search value to query searchable fields against (optional) + * @param page (optional) + * @param size (optional) + * @param sort (optional) + * @return OK (status code 200) + * or Bad Request (status code 400) + */ + @Override + public ResponseEntity<SimulationPage> simulationReadByQuery(String query, String search, Integer page, Integer size, List<String> sort) { + LOGGER.info("Simulation read for size - {}", size); + return ResponseEntity + .status(HttpStatus.OK) + .body(simulationService.simulationReadByQuery(query, search, page, size, sort)); + } + + /** + * GET /api/simulation/track : is track associated with simulation + * is track associated with simulation + * + * @param id The track id (required) + * @return OK (status code 200) + * or Bad Request (status code 400) + * or Not Found (status code 404) + */ + @Override + public ResponseEntity<Boolean> isTrackAssociatedWithSimulation(UUID id) { + LOGGER.info("check track id is associated with simulation : {}", id); + return ResponseEntity + .status(HttpStatus.OK) + .body(simulationService.isTrackAssociatedWithSimulation(id)); + } +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/simulation/entity/SimulationEntity.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/simulation/entity/SimulationEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..f7dcf6cbc40cdc06d5a396d2a801c246d4bfe691 --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/simulation/entity/SimulationEntity.java @@ -0,0 +1,94 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.simulation.entity; + +import lombok.*; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; +import com.tsystems.dco.model.ScenarioType; + +import jakarta.persistence.*; +import java.time.Instant; +import java.util.List; +import java.util.UUID; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@EntityListeners({ + AuditingEntityListener.class +}) +@Entity(name = "simulation") +public class SimulationEntity { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "id") + private UUID id; + + @Column(name = "name") + private String name; + + @Column(name = "environment") + private String environment; + + @Column(name = "platform") + private String platform; + + @Column(name = "scenario_type") + private ScenarioType scenarioType; + + @Column(name = "status") + private String status; + + @Column(name = "hardware") + private String hardware; + + @Column(name = "description") + private String description; + + @CreatedDate + @Column(name = "created_at") + private Instant createdAt; + + @Column(name = "tracks") + @ElementCollection(targetClass = UUID.class) + private List<UUID> tracks; + + @Column(name = "scenarios") + @ElementCollection(targetClass = UUID.class) + private List<UUID> scenarios; + + @Column(name = "campaign_id") + private UUID campaignId; + + @CreatedDate + @Column(name = "start_date") + private Instant startDate; + + @Column(name = "created_by") + private String createdBy; + +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/simulation/repository/SimulationRepository.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/simulation/repository/SimulationRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..b8cab2045822b4afa9fd43a090c63eeddfeedb75 --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/simulation/repository/SimulationRepository.java @@ -0,0 +1,33 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.simulation.repository; + +import com.tsystems.dco.simulation.entity.SimulationEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import java.util.UUID; + +public interface SimulationRepository extends JpaRepository<SimulationEntity, UUID>, JpaSpecificationExecutor<SimulationEntity> { +} + diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/simulation/service/CampaignService.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/simulation/service/CampaignService.java new file mode 100644 index 0000000000000000000000000000000000000000..55d0bd312cc30b05951e1eed62b4a765ce8cee3c --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/simulation/service/CampaignService.java @@ -0,0 +1,78 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.simulation.service; + +import com.tsystems.dco.integration.Campaign; +import com.tsystems.dco.integration.CampaignRequest; +import lombok.SneakyThrows; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +@Service +public class CampaignService { + + private static final Logger LOGGER = LoggerFactory.getLogger(CampaignService.class); + + /** + * @param campaignRequest + * @return Campaign + */ + public Campaign startCampaign(CampaignRequest campaignRequest) { + LOGGER.info("campaign Request {}", campaignRequest); + //calling campaign service and get the campaign id as response + //returning mocked campaign id + + return Campaign.builder().id(UUID.randomUUID()).status("Running").build(); + } + + /** + * @param campaignId + * @return Campaign + */ + public Campaign checkStatus(UUID campaignId) { + //calling campaign service and get the campaign status as response + // returning mocked campaign response + return Campaign.builder() + .id(campaignId) + .status(getMockedCampaignStatus()) + .build(); + } + + /** + * @return String + */ + @SneakyThrows + private String getMockedCampaignStatus() { + List<String> mockedStatuses = Arrays.asList("Pending", "Running", "Done", "Error", "Timeout"); + var random = new SecureRandom(); + var index = random.nextInt(mockedStatuses.size()); + return mockedStatuses.get(index); + } +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/simulation/service/SimulationQuery.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/simulation/service/SimulationQuery.java new file mode 100644 index 0000000000000000000000000000000000000000..009a899a74694cd2d62fba7adca056c5dc66a0ed --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/simulation/service/SimulationQuery.java @@ -0,0 +1,99 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.simulation.service; + +import com.tsystems.dco.exception.BaseException; +import com.tsystems.dco.simulation.entity.SimulationEntity; +import com.tsystems.dco.util.Operation; +import com.tsystems.dco.util.QueryUtil; +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.http.HttpStatus; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +@Getter +public class SimulationQuery { + + + private final List<Query> queries = new ArrayList<>(); + + public SimulationQuery(String query) { + var matcher = QueryUtil.QUERY_PATTERN.matcher(query + ","); + while (matcher.find()) { + queries.add(new Query( + matcher.group(1), + Operation.from(matcher.group(2).charAt(0)), + matcher.group(3)) + ); + } + } + + public SimulationQuery isValid() { + if (queries.isEmpty()) { + throw new BaseException(HttpStatus.BAD_REQUEST, "The query does not match the valid pattern."); + } + return this; + } + + public Specification<SimulationEntity> toSpecification() { + if (queries.isEmpty()) { + return null; + } + Specification<SimulationEntity> result = queries.get(0); + for (var i = 1; i < queries.size(); i++) { + result = Specification.where(result).and(queries.get(i)); + } + return result; + } + + @Builder + @RequiredArgsConstructor + public static class Query implements Serializable, Specification<SimulationEntity> { + private final String field; + private final Operation operation; + private final String value; + + @Override + public Predicate toPredicate( + Root<SimulationEntity> root, + CriteriaQuery<?> query, + CriteriaBuilder builder + ) { + return switch (operation) { + case EQUAL -> builder.equal(root.get(field), value); + case NOT_EQUAL -> builder.notEqual(root.get(field), value); + case LIKE -> builder.like(root.get(field), "%" + value + "%"); + }; + } + } +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/simulation/service/SimulationQueryUtil.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/simulation/service/SimulationQueryUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..d2dc160ffee5a9ae82594851f5cec2160e73a8fc --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/simulation/service/SimulationQueryUtil.java @@ -0,0 +1,61 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.simulation.service; + +import com.tsystems.dco.simulation.entity.SimulationEntity; +import org.springframework.data.jpa.domain.Specification; +import com.tsystems.dco.util.Operation; +import java.util.Optional; + +public class SimulationQueryUtil { + + private SimulationQueryUtil() { + } + + /** + * @param query + * @param search + * @return Specification + */ + public static Specification<SimulationEntity> getSimulationQuerySpecification(String query, String search) { + return Optional.ofNullable(search) + .map(s -> Specification + .where(SimulationQuery.Query.builder() + .field("name").operation(Operation.LIKE).value(s) + .build()) + .or(SimulationQuery.Query.builder() + .field("status").operation(Operation.LIKE).value(s) + .build()) + .or(SimulationQuery.Query.builder() + .field("scenarioType").operation(Operation.LIKE).value(s) + .build()) + .or(SimulationQuery.Query.builder() + .field("createdBy").operation(Operation.LIKE).value(s) + .build()) + .or(SimulationQuery.Query.builder() + .field("createdAt").operation(Operation.LIKE).value(s) + .build())) + .orElse(new SimulationQuery(query).toSpecification()); + } +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/simulation/service/SimulationService.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/simulation/service/SimulationService.java new file mode 100644 index 0000000000000000000000000000000000000000..90e74c7b439e3e2c24abc188204c01f7d5e1c3f8 --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/simulation/service/SimulationService.java @@ -0,0 +1,39 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.simulation.service; + +import com.tsystems.dco.model.SimulationInput; +import com.tsystems.dco.model.SimulationPage; + +import java.util.List; +import java.util.UUID; + +public interface SimulationService { + + String launchSimulation(SimulationInput simulationInput); + + SimulationPage simulationReadByQuery(String query, String search, Integer page, Integer size, List<String> sort); + + boolean isTrackAssociatedWithSimulation(UUID trackId); +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/simulation/service/SimulationServiceImpl.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/simulation/service/SimulationServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..11c8c7277bbf3c18e188cc80e55c9c88c43f0970 --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/simulation/service/SimulationServiceImpl.java @@ -0,0 +1,166 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.simulation.service; + +import com.tsystems.dco.exception.DataNotFoundException; +import com.tsystems.dco.integration.*; +import com.tsystems.dco.mapper.SimulationMapper; +import com.tsystems.dco.model.Simulation; +import com.tsystems.dco.model.SimulationInput; +import com.tsystems.dco.model.SimulationPage; +import com.tsystems.dco.scenario.repository.ScenarioRepository; +import com.tsystems.dco.simulation.entity.SimulationEntity; +import com.tsystems.dco.simulation.repository.SimulationRepository; +import com.tsystems.dco.util.QueryUtil; +import feign.FeignException; +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +@Service +@RequiredArgsConstructor +public class SimulationServiceImpl implements SimulationService { + + private static final Logger LOGGER = LoggerFactory.getLogger(SimulationServiceImpl.class); + + private final SimulationRepository simulationRepository; + private final TrackRepositoryApiClient trackRepositoryApiClient; + private final CampaignService campaignService; + private final ScenarioRepository scenarioRepository; + + + /** + * @param simulationInput + * @return String + */ + @Override + public String launchSimulation(SimulationInput simulationInput) { + isScenariosExists(simulationInput.getScenarios()); + isTracksExists(simulationInput.getTracks()); + SimulationEntity simulationEntity = SimulationMapper.INSTANCE.toEntity(simulationInput); + //start campaign for the simulation + var campaignRequest = CampaignRequest.builder().name(simulationInput.getName()).build(); + Campaign campaign = campaignService.startCampaign(campaignRequest); + LOGGER.info("campaign Id {} for simulation {}", campaign.getId(), simulationInput.getName()); + simulationEntity.setCampaignId(campaign.getId()); + simulationEntity.setStatus(campaign.getStatus()); + SimulationEntity simulation = simulationRepository.save(simulationEntity); + return "Simulation launched with id : " + simulation.getId(); + } + + /** + * @param scenarios + */ + private void isScenariosExists(List<UUID> scenarios) { + scenarios.forEach(scenario -> { + if (!scenarioRepository.existsById(scenario)) { + throw new DataNotFoundException(HttpStatus.BAD_REQUEST, "Scenario doesn't exists"); + } + }); + } + + /** + * @param tracks + */ + private void isTracksExists(List<UUID> tracks) { + try { + trackRepositoryApiClient.isTracksExists(tracks); + } catch (FeignException.BadRequest e) { + LOGGER.error("error {}", e.getMessage()); + throw new DataNotFoundException(HttpStatus.BAD_REQUEST, "Track doesn't exists"); + } + } + + + /** + * @param query + * @param search + * @param page + * @param size + * @param sort + * @return SimulationPage + */ + @Override + public SimulationPage simulationReadByQuery(String query, String search, Integer page, Integer size, List<String> sort) { + var pageable = QueryUtil.getPageRequest(page, size, sort); + var specs = SimulationQueryUtil.getSimulationQuerySpecification(query, search); + var queried = simulationRepository.findAll(specs, pageable); + + List<Simulation> simulations = new ArrayList<>(); + List<SimulationEntity> simulationEntities = queried.getContent(); + simulationEntities.forEach(simulation -> { + List<UUID> trackIds = simulation.getTracks(); + List<Track> tracks = trackRepositoryApiClient.findTrackByIds(trackIds).getBody(); + IntStream vehicleStream = tracks.stream().mapToInt(v -> v.getVehicles().size()); + List<String> brands = tracks.stream().flatMap(track -> track.getVehicles().stream().map(VehicleResponse::getBrand)).collect(Collectors.toList()); + Simulation sim = Simulation.builder().id(simulation.getId()) + .name(simulation.getName()) + .platform(simulation.getPlatform()) + .environment(simulation.getEnvironment()) + .scenarioType(simulation.getScenarioType()) + .startDate(simulation.getStartDate()) + .status(campaignService.checkStatus(simulation.getCampaignId()).getStatus()) + .noOfVehicle(vehicleStream.sum()) + .brands(brands) + .hardware(simulation.getHardware()) + .noOfScenarios(simulation.getScenarios().size()) + .createdBy(simulation.getCreatedBy()) + .description(simulation.getDescription()) + .build(); + simulations.add(sim); + }); + + return new SimulationPage() + .empty(queried.isEmpty()) + .first(queried.isFirst()) + .last(queried.isLast()) + .page(queried.getNumber()) + .size(queried.getSize()) + .pages(queried.getTotalPages()) + .elements(queried.getNumberOfElements()) + .total(queried.getTotalElements()) + .content(simulations); + } + + /** + * @param trackId + * @return boolean + */ + @Override + public boolean isTrackAssociatedWithSimulation(UUID trackId) { + List<SimulationEntity> simulationEntities = simulationRepository.findAll(); + List<SimulationEntity> matchedData = simulationEntities.stream().filter(sim -> sim.getTracks().stream() + .anyMatch(t -> t.equals(trackId))).collect(Collectors.toList()); + return !matchedData.isEmpty(); + } + +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/util/FileUtil.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/util/FileUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..678ce9c9758421c6b746a4d378be4dfef41f6636 --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/util/FileUtil.java @@ -0,0 +1,36 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.util; + +public class FileUtil { + private FileUtil() { + throw new IllegalStateException("Utility class"); + } + + private static final String FILE_NAME_PATTERN = "^[A-Za-z0-9]+([-_.][A-Za-z0-9]+)*+$"; + + public static boolean validateFilename(String fileName) { + return fileName.matches(FILE_NAME_PATTERN); + } +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/util/Operation.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/util/Operation.java new file mode 100644 index 0000000000000000000000000000000000000000..58fbb22ab064710397c40268d49cb9c5c3bd4c8c --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/util/Operation.java @@ -0,0 +1,37 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.util; + +public enum Operation { + EQUAL, NOT_EQUAL, LIKE; + + public static Operation from(final char input) { + return switch (input) { + case ':' -> EQUAL; + case '!' -> NOT_EQUAL; + case '~' -> LIKE; + default -> null; + }; + } +} diff --git a/scenario-library-service/app/src/main/java/com/tsystems/dco/util/QueryUtil.java b/scenario-library-service/app/src/main/java/com/tsystems/dco/util/QueryUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..0c1987bcf4084fc07bdb28b4d195dfed322c7a88 --- /dev/null +++ b/scenario-library-service/app/src/main/java/com/tsystems/dco/util/QueryUtil.java @@ -0,0 +1,63 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.util; + +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +public class QueryUtil { + + private QueryUtil(){} + public static final Sort.Order ORDER_DEFAULT = Sort.Order.asc("createdAt"); + public static final Integer PAGEABLE_DEFAULT_PAGE = 0; + public static final Integer PAGEABLE_DEFAULT_SIZE = 15; + public static final Pattern QUERY_PATTERN = Pattern.compile("([\\w.]+?)([:!~])(.*?),"); + + /** + * @param page + * @param size + * @param sort + * @return PageRequest + */ + public static PageRequest getPageRequest(Integer page, Integer size, List<String> sort) { + var orders = Optional.ofNullable(sort) + .map(s -> s.stream() + .map(so -> so.split(":")) + .map(sp -> sp.length >= 2 ? new Sort.Order(Sort.Direction.fromString(sp[1]), sp[0]) : null) + .filter(Objects::nonNull) + ).orElse(Stream.of(QueryUtil.ORDER_DEFAULT)) + .toList(); + return PageRequest.of( + Optional.ofNullable(page).orElse(QueryUtil.PAGEABLE_DEFAULT_PAGE), + Optional.ofNullable(size).orElse(QueryUtil.PAGEABLE_DEFAULT_SIZE), + Sort.by(orders) + ); + } +} diff --git a/scenario-library-service/app/src/main/resources/application.yml b/scenario-library-service/app/src/main/resources/application.yml new file mode 100644 index 0000000000000000000000000000000000000000..8698315f01cf5d4c2be4b1196c1e5969f8af5ede --- /dev/null +++ b/scenario-library-service/app/src/main/resources/application.yml @@ -0,0 +1,65 @@ +app: + rest: + port: 8082 + probes: + port: 8082 + cors: + origins: "*" + headers: "*" + postgres: + host: localhost + port: 5432 + database: postgres + username: postgres + password: postgres + storage: + url: http://localhost:9000 + access.key: none + secret.key: none + bucket: scenario-library-service + username: developer + password: password +track-service: + url: http://localhost:8081 +server: + port: ${app.rest.port} + forward-headers-strategy: FRAMEWORK +spring: + datasource: + driver-class-name: org.postgresql.Driver + url: jdbc:postgresql://${app.postgres.host}:${app.postgres.port}/${app.postgres.database} + username: ${app.postgres.username} + password: ${app.postgres.password} + jpa: + database-platform: org.hibernate.dialect.PostgreSQLDialect + hibernate: + ddl-auto: none + show-sql: true +springdoc: + api-docs: + path: /openapi + swagger-ui: + path: /openapi/ui +management: + server: + port: ${app.probes.port} + info: + env: + enabled: true + build: + enabled: false + endpoint: + info: + enabled: true + health: + enabled: true + metrics: + enabled: true + prometheus: + enabled: true + endpoints: + enabled-by-default: false + web: + base-path: /management + exposure: + include: info,health,metrics,prometheus diff --git a/scenario-library-service/app/src/test/java/com/tsystems/dco/file/service/FileStorageServiceTest.java b/scenario-library-service/app/src/test/java/com/tsystems/dco/file/service/FileStorageServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..689bfd67dbb7269ea110e43f7f5c52d334958f2c --- /dev/null +++ b/scenario-library-service/app/src/test/java/com/tsystems/dco/file/service/FileStorageServiceTest.java @@ -0,0 +1,78 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.file.service; + +import com.tsystems.dco.file.config.S3Config; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.web.multipart.MultipartFile; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.BDDMockito.given; + +@ExtendWith(MockitoExtension.class) +class FileStorageServiceTest { + + @InjectMocks + private FileStorageServiceImpl fileStorageService; + @Mock + private MultipartFile file; + @Mock + private S3Config s3Config; + + private final String TEST = "TEST"; + + @Test + void getFilePath() throws NoSuchMethodException, SecurityException, + IllegalAccessException, IllegalArgumentException, InvocationTargetException { + given(s3Config.getBucketName()).willReturn(TEST); + given(s3Config.getMinioEndpoint()).willReturn("http://endpoint"); + Method method = FileStorageServiceImpl.class.getDeclaredMethod("getFilePath", String.class); + method.setAccessible(true); + assertEquals("http://endpoint/TEST/TEST", (String) method.invoke(fileStorageService, TEST)); + } + + @Test + void createObjectNameForFile() throws NoSuchMethodException, SecurityException, + IllegalAccessException, IllegalArgumentException, InvocationTargetException { + UUID uuid = UUID.randomUUID(); + Method method = FileStorageServiceImpl.class.getDeclaredMethod("createObjectNameForFile", UUID.class, String.class); + method.setAccessible(true); + assertEquals("scenario/" + uuid + "/files/TEST", (String) method.invoke(fileStorageService, uuid, TEST)); + } + + @Test + void deleteFileWithError() { + given(s3Config.getBucketName()).willReturn(TEST); + assertThrows(NullPointerException.class, () -> fileStorageService.deleteFile(TEST)); + } +} diff --git a/scenario-library-service/app/src/test/java/com/tsystems/dco/scenario/controller/ScenarioControllerTest.java b/scenario-library-service/app/src/test/java/com/tsystems/dco/scenario/controller/ScenarioControllerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..47b5f07855e927cfaaef666ec9fba1b9422fdcab --- /dev/null +++ b/scenario-library-service/app/src/test/java/com/tsystems/dco/scenario/controller/ScenarioControllerTest.java @@ -0,0 +1,141 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.scenario.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.tsystems.dco.App; +import com.tsystems.dco.model.ScenarioInput; +import com.tsystems.dco.scenario.service.ScenarioService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.request.RequestPostProcessor; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.UUID; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.any; +import static org.mockito.Mockito.verify; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@ContextConfiguration(classes = App.class) +@AutoConfigureMockMvc +class ScenarioControllerTest { + + @Autowired + private MockMvc mockMvc; + @Autowired + private ObjectMapper objectMapper; + @MockBean + private ScenarioService scenarioService; + private static final String TEST = "TEST"; + + @Test + void createScenario() throws Exception { + ScenarioInput scenarioInput = ScenarioInput.builder().name(TEST).build(); + MockMultipartFile file = new MockMultipartFile(TEST, TEST, MediaType.APPLICATION_PDF_VALUE, TEST.getBytes(StandardCharsets.UTF_8)); + MockMultipartFile metadata = new MockMultipartFile(TEST, TEST, MediaType.APPLICATION_JSON_VALUE, + objectMapper.writeValueAsString(scenarioInput).getBytes(StandardCharsets.UTF_8)); + mockMvc.perform( + multipart("/api/scenario") + .file(file) + .file(metadata) + .header("Authorization", getHeader()) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()); + verify(scenarioService).createScenario(any(), any()); + } + + @Test + void deleteScenarioById() throws Exception { + mockMvc.perform(delete("/api/scenario?id=" + UUID.randomUUID()) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", getHeader())) + .andDo(print()) + .andExpect(status().isNoContent()).andReturn(); + verify(scenarioService).deleteScenarioById(any()); + } + + @Test + void scenarioReadByQuery() throws Exception { + mockMvc.perform(get("/api/scenario") + .param("page", "0") + .param("size", "10") + .header("Authorization", getHeader()) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()).andReturn(); + verify(scenarioService).readScenarioByQuery(any(), any(), any(), any(), any()); + } + + @Test + void searchScenarioByPattern() throws Exception { + mockMvc.perform(get("/api/scenario/search") + .param("scenarioPattern", "t") + .param("page", "0") + .param("size", "10") + .header("Authorization", getHeader()) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()).andReturn(); + verify(scenarioService).searchScenarioByPattern(anyString(), anyInt(), anyInt()); + } + + @Test + void scenarioUpdateById() throws Exception { + MockMultipartFile file = new MockMultipartFile(TEST, TEST,"text/plain", TEST.getBytes()); + MockMultipartHttpServletRequestBuilder builder = MockMvcRequestBuilders.multipart("/api/scenario"); + builder.with(new RequestPostProcessor() { + @Override + public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { + request.setMethod("PUT"); + return request; + } + }); + mockMvc.perform(builder + .file(file) + .param("id", String.valueOf(UUID.randomUUID())) + .header("Authorization", getHeader())) + .andExpect(status().isOk()); + } + + private String getHeader(){ + return "Basic "+ Base64.getEncoder().encodeToString("developer:password".getBytes()); + } +} diff --git a/scenario-library-service/app/src/test/java/com/tsystems/dco/scenario/service/ScenarioQueryTest.java b/scenario-library-service/app/src/test/java/com/tsystems/dco/scenario/service/ScenarioQueryTest.java new file mode 100644 index 0000000000000000000000000000000000000000..614124c88acf070d227bf8b81c513bf524d6d3de --- /dev/null +++ b/scenario-library-service/app/src/test/java/com/tsystems/dco/scenario/service/ScenarioQueryTest.java @@ -0,0 +1,46 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.scenario.service; + +import com.tsystems.dco.exception.BaseException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class ScenarioQueryTest { + + @Test + void getQueries() { + var raw = "brand:vw,brand!bmw,brand~benz,brand$invalid"; + var query = new ScenarioQuery(raw); + assertEquals(3, query.getQueries().size()); + var spec = query.toSpecification(); + assertNotNull(spec); + try { + new ScenarioQuery("brandvw").isValid().toSpecification(); + } catch (Exception e) { + assertInstanceOf(BaseException.class, e); + } + } +} diff --git a/scenario-library-service/app/src/test/java/com/tsystems/dco/scenario/service/ScenarioServiceTest.java b/scenario-library-service/app/src/test/java/com/tsystems/dco/scenario/service/ScenarioServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..e7dcbeb16225729f246b5927cdf4681ca32e9aff --- /dev/null +++ b/scenario-library-service/app/src/test/java/com/tsystems/dco/scenario/service/ScenarioServiceTest.java @@ -0,0 +1,150 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.scenario.service; + +import com.tsystems.dco.exception.DataDeletionException; +import com.tsystems.dco.exception.DataNotFoundException; +import com.tsystems.dco.file.entity.FileEntity; +import com.tsystems.dco.file.service.FileStorageService; +import com.tsystems.dco.model.FileData; +import com.tsystems.dco.model.Scenario; +import com.tsystems.dco.model.ScenarioPage; +import com.tsystems.dco.scenario.entity.ScenarioEntity; +import com.tsystems.dco.scenario.repository.ScenarioRepository; +import com.tsystems.dco.simulation.entity.SimulationEntity; +import com.tsystems.dco.simulation.repository.SimulationRepository; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class ScenarioServiceTest { + + @InjectMocks + private ScenarioServiceImpl scenarioService; + @Mock + private ScenarioRepository scenarioRepository; + @Mock + private FileStorageService fileStorageService; + @Mock + private PageRequest pageRequest; + @Mock + private SimulationRepository simulationRepository; + @Mock + private Page<ScenarioEntity> scenarioEntityPage; + + private final String TEST = "TEST"; + + + @Test + void createScenario() { + String scenarioInput = "{\"name\":\"TEST\",\"description\":\"TEST\",\"lastModifiedBy\":\"TEST\",\"createdBy\":\"TEST\"}"; + ScenarioEntity entity = ScenarioEntity.builder().name(TEST).description(TEST).createdBy(TEST).build(); + given(scenarioRepository.save(any())).willReturn(entity); + given(fileStorageService.uploadFile(any(), any(), any())).willReturn(FileData.builder().build()); + MultipartFile file = new MockMultipartFile(TEST, TEST.getBytes()); + Scenario scenario = scenarioService.createScenario(scenarioInput, file); + assertEquals(TEST, scenario.getName()); + verify(scenarioRepository, times(2)).save(any()); + } + + @Test + void createScenarioWithException() { + String scenarioInput = "{\"name\":\"TEST\",\"description\":\"TEST\",\"lastModifiedBy\":\"TEST\",\"createdBy\":\"TEST\"}"; + given(scenarioRepository.save(any())).willThrow(DataNotFoundException.class); + MultipartFile file = new MockMultipartFile(TEST, TEST.getBytes()); + assertThrows(DataNotFoundException.class, () -> scenarioService.createScenario(scenarioInput, file)); + } + + @Test + void readScenarioByQuery(){ + given(scenarioRepository.findAll(any(Specification.class), any(PageRequest.class))).willReturn(scenarioEntityPage); + assertNotNull(scenarioService.readScenarioByQuery(TEST, TEST, 0, 10, null)); + } + + @Test + void deleteScenarioById() { + UUID uuid = UUID.randomUUID(); + ScenarioEntity scenarioEntity = ScenarioEntity.builder().name(TEST).description(TEST).createdBy(TEST).build(); + given(scenarioRepository.findById(uuid)).willReturn(Optional.of(scenarioEntity)); + List<UUID> scenarioIds = Arrays.asList(UUID.randomUUID(), uuid); + List<SimulationEntity> simulationEntities = Arrays.asList(SimulationEntity.builder().id(UUID.randomUUID()).scenarios(scenarioIds).build()); + given(simulationRepository.findAll()).willReturn(simulationEntities); + scenarioService.deleteScenarioById(uuid); + verify(scenarioRepository).findById(any()); + } + + @Test + void deleteScenarioByIdWithError() { + UUID uuid = UUID.randomUUID(); + ScenarioEntity scenarioEntity = ScenarioEntity.builder().name(TEST).id(uuid).createdBy(TEST).build(); + given(scenarioRepository.findById(uuid)).willReturn(Optional.of(scenarioEntity)); + List<UUID> scenarioIds = Arrays.asList(uuid, UUID.randomUUID()); + List<SimulationEntity> simulationEntities = Arrays.asList(SimulationEntity.builder().scenarios(scenarioIds).build()); + given(simulationRepository.findAll()).willReturn(simulationEntities); + assertThrows(DataDeletionException.class, () -> scenarioService.deleteScenarioById(uuid)); + } + + @Test + void scenarioUpdateById() { + UUID uuid = UUID.randomUUID(); + String scenarioInput = "{\"name\":\"TEST\",\"type\":\"MQTT\",\"lastModifiedBy\":\"TEST\",\"createdBy\":\"TEST\"}"; + FileEntity fileEntity = FileEntity.builder().fileKey(TEST).build(); + ScenarioEntity scenarioEntity = ScenarioEntity.builder().id(uuid).name(TEST).file(fileEntity).createdBy(TEST).build(); + given(scenarioRepository.findById(any())).willReturn(Optional.of(scenarioEntity)); + doNothing().when(fileStorageService).deleteFile(any()); + given(fileStorageService.uploadFile(any(), any(), any())).willReturn(FileData.builder().build()); + MultipartFile file = new MockMultipartFile(TEST, TEST.getBytes()); + scenarioService.scenarioUpdateById(uuid, scenarioInput, file); + verify(scenarioRepository).save(any()); + } + + @Test + void searchScenarioByPattern() { + given(scenarioEntityPage.getPageable()).willReturn(pageRequest); + given(scenarioEntityPage.getPageable().getPageNumber()).willReturn(1); + given(scenarioEntityPage.getPageable().getPageSize()).willReturn(1); + given(scenarioRepository.findScenarioByLike(anyString(), any())).willReturn(scenarioEntityPage); + ScenarioPage scenarioPage = scenarioService.searchScenarioByPattern(TEST, 0, 10); + assertEquals(1, scenarioPage.getPage()); + verify(scenarioRepository).findScenarioByLike(anyString(), any()); + } +} diff --git a/scenario-library-service/app/src/test/java/com/tsystems/dco/simulation/controller/SimulationControllerTest.java b/scenario-library-service/app/src/test/java/com/tsystems/dco/simulation/controller/SimulationControllerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b04e18f4621024d89e92c22bcebc5a3af09f42c1 --- /dev/null +++ b/scenario-library-service/app/src/test/java/com/tsystems/dco/simulation/controller/SimulationControllerTest.java @@ -0,0 +1,102 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.simulation.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.tsystems.dco.App; +import com.tsystems.dco.model.SimulationInput; +import com.tsystems.dco.model.SimulationPage; +import com.tsystems.dco.simulation.service.SimulationService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.Base64; +import java.util.UUID; + +import static org.mockito.BDDMockito.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ContextConfiguration(classes = App.class) +@AutoConfigureMockMvc +class SimulationControllerTest { + + @Autowired + private MockMvc mockMvc; + @MockBean + private SimulationService simulationService; + private static final String TEST = "test"; + + @Test + void launchSimulation() throws Exception { + given(simulationService.launchSimulation(any())).willReturn(TEST); + SimulationInput simulationInput = SimulationInput.builder().platform(TEST).build(); + ObjectMapper objectMapper = new ObjectMapper(); + mockMvc.perform(post("/api/simulation") + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", getHeader()) + .content(objectMapper.writeValueAsString(simulationInput))) + .andDo(print()) + .andExpect(status().isCreated()).andReturn(); + verify(simulationService).launchSimulation(any()); + } + + @Test + void simulationReadByQuery() throws Exception { + given(simulationService.simulationReadByQuery(any(), any(), any(), any(), any())).willReturn(new SimulationPage()); + mockMvc.perform(get("/api/simulation") + .param("page", "10") + .header("Authorization", getHeader()) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()).andReturn().getResponse(); + verify(simulationService).simulationReadByQuery(any(), any(), any(), any(), any()); + } + + @Test + void isTrackAssociatedWithSimulation() throws Exception { + given(simulationService.isTrackAssociatedWithSimulation(any())).willReturn(true); + mockMvc.perform(get("/api/simulation/track?id=" + UUID.randomUUID()) + .header("Authorization", getHeader()) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()).andReturn().getResponse(); + verify(simulationService).isTrackAssociatedWithSimulation(any()); + } + + private String getHeader(){ + return "Basic "+ Base64.getEncoder().encodeToString("developer:password".getBytes()); + } +} diff --git a/scenario-library-service/app/src/test/java/com/tsystems/dco/simulation/service/CampaignServiceTest.java b/scenario-library-service/app/src/test/java/com/tsystems/dco/simulation/service/CampaignServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..ce365b5a5f085bd29e157c2a10ea00b892fc2536 --- /dev/null +++ b/scenario-library-service/app/src/test/java/com/tsystems/dco/simulation/service/CampaignServiceTest.java @@ -0,0 +1,51 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.simulation.service; + +import com.tsystems.dco.integration.CampaignRequest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +@ExtendWith(MockitoExtension.class) +class CampaignServiceTest { + + @InjectMocks + private CampaignService campaignService; + + @Test + void startCampaign() { + assertEquals("Running", campaignService.startCampaign(CampaignRequest.builder().build()).getStatus()); + } + + @Test + void checkStatus() { + assertNotNull(campaignService.checkStatus(UUID.randomUUID()).getStatus()); + } +} diff --git a/scenario-library-service/app/src/test/java/com/tsystems/dco/simulation/service/SimulationQueryTest.java b/scenario-library-service/app/src/test/java/com/tsystems/dco/simulation/service/SimulationQueryTest.java new file mode 100644 index 0000000000000000000000000000000000000000..8c3bbfa8351fa988527bcf6433111fc3abec987d --- /dev/null +++ b/scenario-library-service/app/src/test/java/com/tsystems/dco/simulation/service/SimulationQueryTest.java @@ -0,0 +1,46 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.simulation.service; + +import com.tsystems.dco.exception.BaseException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class SimulationQueryTest { + + @Test + void getQueries() { + var raw = "brand:vw,brand!bmw,brand~benz,brand$invalid"; + var query = new SimulationQuery(raw); + assertEquals(3, query.getQueries().size()); + var spec = query.toSpecification(); + assertNotNull(spec); + try { + new SimulationQuery("brandvw").isValid().toSpecification(); + } catch (Exception e) { + assertInstanceOf(BaseException.class, e); + } + } +} diff --git a/scenario-library-service/app/src/test/java/com/tsystems/dco/simulation/service/SimulationServiceTest.java b/scenario-library-service/app/src/test/java/com/tsystems/dco/simulation/service/SimulationServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..8c0f0af219fb7f7a9b1393b351369c57badd4847 --- /dev/null +++ b/scenario-library-service/app/src/test/java/com/tsystems/dco/simulation/service/SimulationServiceTest.java @@ -0,0 +1,155 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.simulation.service; + +import com.tsystems.dco.exception.DataNotFoundException; +import com.tsystems.dco.integration.Campaign; +import com.tsystems.dco.integration.Track; +import com.tsystems.dco.integration.TrackRepositoryApiClient; +import com.tsystems.dco.integration.VehicleResponse; +import com.tsystems.dco.model.SimulationInput; +import com.tsystems.dco.scenario.repository.ScenarioRepository; +import com.tsystems.dco.simulation.entity.SimulationEntity; +import com.tsystems.dco.simulation.repository.SimulationRepository; +import feign.FeignException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.http.ResponseEntity; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class SimulationServiceTest { + + @InjectMocks + private SimulationServiceImpl simulationService; + @Mock + private ScenarioRepository scenarioRepository; + @Mock + private SimulationRepository simulationRepository; + @Mock + private CampaignService campaignService; + @Mock + private TrackRepositoryApiClient trackRepositoryApiClient; + @Mock + private Page<SimulationEntity> page; + @Mock + private ResponseEntity<List<Track>> trackResponseEntity; + private final String TEST = "TEST"; + + @Test + void launchSimulation() { + List<UUID> uuids = Arrays.asList(UUID.randomUUID(), UUID.randomUUID()); + given(scenarioRepository.existsById(any())).willReturn(true); + given(campaignService.startCampaign(any())).willReturn(Campaign.builder().build()); + UUID uuid = UUID.randomUUID(); + SimulationEntity simulation = SimulationEntity.builder().id(uuid).name(TEST).description(TEST).createdBy(TEST).environment(TEST).hardware(TEST).build(); + given(simulationRepository.save(any())).willReturn(simulation); + SimulationInput simulationInput = SimulationInput.builder().scenarios(uuids).tracks(uuids).build(); + assertEquals("Simulation launched with id : " + uuid, simulationService.launchSimulation(simulationInput)); + verify(campaignService).startCampaign(any()); + } + + @Test + void launchSimulationWithScenarioError() { + List<UUID> uuids = Arrays.asList(UUID.randomUUID(), UUID.randomUUID()); + given(scenarioRepository.existsById(any())).willReturn(false); + SimulationInput simulationInput = SimulationInput.builder().scenarios(uuids).tracks(uuids).build(); + assertThrows(DataNotFoundException.class, () -> simulationService.launchSimulation(simulationInput)); + } + + @Test + void launchSimulationWithTrackError() { + List<UUID> uuids = Arrays.asList(UUID.randomUUID(), UUID.randomUUID()); + given(scenarioRepository.existsById(any())).willReturn(true); + given(trackRepositoryApiClient.isTracksExists(uuids)).willThrow(FeignException.BadRequest.class); + SimulationInput simulationInput = SimulationInput.builder().scenarios(uuids).tracks(uuids).build(); + assertThrows(DataNotFoundException.class, () -> simulationService.launchSimulation(simulationInput)); + } + + @Test + void simulationReadByQueryWithError() { + List<String> sort = new ArrayList<>(); + sort.add("name:asc"); + assertThrows(NullPointerException.class, () -> simulationService.simulationReadByQuery(null, TEST, 0, 10, sort)); + } + + @Test + void simulationReadByQuery() { + UUID uuid = UUID.randomUUID(); + SimulationEntity simulation = SimulationEntity.builder().id(uuid).name(TEST).description(TEST) + .createdBy(TEST) + .environment(TEST) + .scenarios(Arrays.asList(UUID.randomUUID(), uuid)) + .hardware(TEST).build(); + List<SimulationEntity> simulationEntities = new ArrayList<>(); + simulationEntities.add(simulation); + given(page.getContent()).willReturn(simulationEntities); + given(simulationRepository.findAll(any(Specification.class), any(PageRequest.class))).willReturn(page); + VehicleResponse vehicleResponse = new VehicleResponse(); + vehicleResponse.setVin(TEST); + vehicleResponse.setBrand(TEST); + vehicleResponse.setCountry(TEST); + vehicleResponse.setModel(TEST); + List<VehicleResponse> vehicleResponses = new ArrayList<>(); + vehicleResponses.add(vehicleResponse); + Track track = new Track(); + track.setId(uuid); + track.setName(TEST); + track.setVehicles(vehicleResponses); + List<Track> tracks = new ArrayList<>(); + tracks.add(track); + given(trackResponseEntity.getBody()).willReturn(tracks); + given(trackRepositoryApiClient.findTrackByIds(any())).willReturn(trackResponseEntity); + Campaign campaign = Campaign.builder().status(TEST).id(uuid).build(); + given(campaignService.checkStatus(any())).willReturn(campaign); + simulationService.simulationReadByQuery(null, TEST, 0, 10, null); + } + + @Test + void isTrackAssociatedWithSimulation() { + UUID uuid = UUID.randomUUID(); + List<UUID> uuids = Arrays.asList(uuid, UUID.randomUUID()); + SimulationEntity simulationEntity = SimulationEntity.builder().tracks(uuids).build(); + List<SimulationEntity> simulationEntities = new ArrayList<>(); + simulationEntities.add(simulationEntity); + given(simulationRepository.findAll()).willReturn(simulationEntities); + simulationService.isTrackAssociatedWithSimulation(UUID.randomUUID()); + } +} diff --git a/scenario-library-service/app/src/test/java/com/tsystems/dco/util/FileUtilTest.java b/scenario-library-service/app/src/test/java/com/tsystems/dco/util/FileUtilTest.java new file mode 100644 index 0000000000000000000000000000000000000000..8a7e53582a27679792f279a689ddc80dfb2a2141 --- /dev/null +++ b/scenario-library-service/app/src/test/java/com/tsystems/dco/util/FileUtilTest.java @@ -0,0 +1,36 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.util; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class FileUtilTest { + + @Test + void validateFilename() { + assertTrue(FileUtil.validateFilename("software.txt")); + } +} diff --git a/scenario-library-service/pom.xml b/scenario-library-service/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..f2843955bc11511cfc9e6cfe8f03a0c33caf87a8 --- /dev/null +++ b/scenario-library-service/pom.xml @@ -0,0 +1,216 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <groupId>com.tsystems.dco</groupId> + <artifactId>scenario-library-service</artifactId> + <packaging>pom</packaging> + <version>latest</version> + <modules> + <module>api</module> + <module>app</module> + <module>app-database</module> + </modules> + + <organization> + <name>T-Systems International GmbH</name> + <url>https://t-systems.com</url> + </organization> + + <developers> + <developer> + <name>T-Systems</name> + <email>info@t-systems.com</email> + <organization>T-Systems International GmbH</organization> + <organizationUrl>https://t-systems.com</organizationUrl> + </developer> + </developers> + + <properties> + <!-- java --> + <java.version>17</java.version> + <maven.compiler.source>17</maven.compiler.source> + <maven.compiler.target>17</maven.compiler.target> + <!-- charset --> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> + <spring.boot.version>3.1.0</spring.boot.version> + <spring.cloud.version>2022.0.3</spring.cloud.version> + <aws.sdk.version>2.17.295</aws.sdk.version> + </properties> + + <dependencyManagement> + <dependencies> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-dependencies</artifactId> + <version>${spring.boot.version}</version> + <type>pom</type> + <scope>import</scope> + </dependency> + <dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-dependencies</artifactId> + <version>${spring.cloud.version}</version> + <type>pom</type> + <scope>import</scope> + </dependency> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + <version>1.18.24</version> + </dependency> + <dependency> + <groupId>org.mapstruct</groupId> + <artifactId>mapstruct</artifactId> + <version>1.4.2.Final</version> + </dependency> + <dependency> + <groupId>org.springdoc</groupId> + <artifactId>springdoc-openapi-ui</artifactId> + <version>1.7.0</version> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>bom</artifactId> + <version>${aws.sdk.version}</version> + <type>pom</type> + <scope>import</scope> + </dependency> + </dependencies> + </dependencyManagement> + + <build> + <pluginManagement> + <plugins> + <plugin> + <groupId>io.github.pmckeown</groupId> + <artifactId>dependency-track-maven-plugin</artifactId> + <version>1.1.3</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-resources-plugin</artifactId> + <version>3.2.0</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.8.1</version> + </plugin> + <plugin> + <groupId>org.openapitools</groupId> + <artifactId>openapi-generator-maven-plugin</artifactId> + <version>6.6.0</version> + </plugin> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + <version>${spring.boot.version}</version> + </plugin> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + <version>0.8.8</version> + </plugin> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + <executions> + <execution> + <id>prepare-agent</id> + <goals> + <goal>prepare-agent</goal> + </goals> + </execution> + <execution> + <id>report</id> + <phase>test</phase> + <goals> + <goal>report</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </pluginManagement> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <version>3.0.0-M5</version> + </plugin> + <plugin> + <groupId>org.sonarsource.scanner.maven</groupId> + <artifactId>sonar-maven-plugin</artifactId> + <version>3.9.1.2184</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-resources-plugin</artifactId> + <configuration> + <delimiters> + <delimiter>@</delimiter> + </delimiters> + <useDefaultDelimiters>false</useDefaultDelimiters> + </configuration> + </plugin> + <plugin> + <groupId>com.github.spotbugs</groupId> + <artifactId>spotbugs-maven-plugin</artifactId> + <configuration> + <failOnError>false</failOnError> + <outputEncoding>UTF-8</outputEncoding> + <xmlOutput>true</xmlOutput> + </configuration> + <executions> + <execution> + <id>check</id> + <phase>package</phase> + <goals> + <goal>check</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.cyclonedx</groupId> + <artifactId>cyclonedx-maven-plugin</artifactId> + <executions> + <execution> + <id>aggregate</id> + <phase>package</phase> + <goals> + <goal>makeAggregateBom</goal> + </goals> + </execution> + </executions> + <configuration> + <projectType>application</projectType> + <schemaVersion>1.4</schemaVersion> + <includeBomSerialNumber>true</includeBomSerialNumber> + <includeCompileScope>true</includeCompileScope> + <includeRuntimeScope>true</includeRuntimeScope> + <includeProvidedScope>false</includeProvidedScope> + <includeSystemScope>true</includeSystemScope> + <includeTestScope>false</includeTestScope> + <includeLicenseText>false</includeLicenseText> + <outputReactorProjects>true</outputReactorProjects> + <outputFormat>all</outputFormat> + <outputName>bom</outputName> + </configuration> + </plugin> + <plugin> + <groupId>io.github.pmckeown</groupId> + <artifactId>dependency-track-maven-plugin</artifactId> + <version>1.1.3</version> + <configuration> + <projectName>${app.name}</projectName> + <projectVersion>latest</projectVersion> + </configuration> + </plugin> + </plugins> + </build> +</project> diff --git a/scenario-library-service/settings.xml b/scenario-library-service/settings.xml new file mode 100644 index 0000000000000000000000000000000000000000..1c684def3e2fee8e8add496c421d243173cf3819 --- /dev/null +++ b/scenario-library-service/settings.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<settings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/SETTINGS/1.0.0" + xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd"> + <localRepository>.m2/repository</localRepository> + <interactiveMode>false</interactiveMode> + <offline/> + <pluginGroups/> + <servers/> + <mirrors/> + <proxies/> + <profiles/> + <activeProfiles/> +</settings> diff --git a/tracks-management-service/.gitignore b/tracks-management-service/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2a27d977fecbbb5a13bbb22af8d71e4db84dcf6 --- /dev/null +++ b/tracks-management-service/.gitignore @@ -0,0 +1,25 @@ +HELP.md +target/ +api/target +app/target +app-database/target +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + + diff --git a/tracks-management-service/Dockerfile.app b/tracks-management-service/Dockerfile.app new file mode 100644 index 0000000000000000000000000000000000000000..1aded6a92cb643c58c5d8c169d92c8c985c388f0 --- /dev/null +++ b/tracks-management-service/Dockerfile.app @@ -0,0 +1,4 @@ +FROM amazoncorretto:17 AS app +COPY tracks-management-service/app/target/*.jar /app/app.jar +WORKDIR /app +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/tracks-management-service/api/openapi/openapi-track.yml b/tracks-management-service/api/openapi/openapi-track.yml new file mode 100644 index 0000000000000000000000000000000000000000..e44b1f505086b036e7268c78142148408e5f8637 --- /dev/null +++ b/tracks-management-service/api/openapi/openapi-track.yml @@ -0,0 +1,544 @@ +# TODO: adapt openapi specification itself and file name +openapi: 3.0.1 +info: + title: openapi-track + version: latest +servers: + - url: http://localhost:8080 +tags: + - name: Track + description: The endpoints for track interactions + - name: Vehicle + description: The endpoints for vehicle interactions +paths: + /api/track: + post: + tags: + - Track + summary: Create a Track + description: Create a Track to database + operationId: createTrack + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/TrackInput" + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/Track" + "400": + description: Bad Request + "404": + description: Not Found + get: + tags: + - Track + summary: Read Track by query + description: Read Track by query and pageable from database + operationId: trackReadByQuery + parameters: + - name: query + in: query + required: false + example: brand:vw,brand!bmw,brand~benz + description: >- + Comma separated list of `{field}{operation}{value}` where operation can be + `:` for equal, + `!` for not equal and + `~` for like operation + schema: + type: string + - name: search + in: query + required: false + description: Search value to query searchable fields agains + schema: + type: string + - name: page + in: query + required: false + example: 0 + schema: + type: integer + format: int32 + - name: size + in: query + required: false + example: 15 + schema: + type: integer + format: int32 + - name: sort + in: query + required: false + example: + - name:asc + schema: + type: array + items: + type: string + example: name:asc + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/TrackPage" + "404": + description: Not Found + delete: + tags: + - Track + summary: Delete a track by id + description: Delete a track by id from database + operationId: deleteTrackById + parameters: + - name: id + in: query + description: The track id + required: true + schema: + type: string + format: uuid + responses: + "200": + description: OK + content: + application/json: + schema: + type: string + "400": + description: Bad Request + "404": + description: Not Found + /api/track/{id}: + get: + tags: + - Track + summary: Find track by id + description: Find track by id + operationId: findTrackById + parameters: + - name: id + in: path + description: The track id + required: true + schema: + type: string + format: uuid + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/Track" + "404": + description: Not Found + /api/track/validate: + get: + tags: + - Track + summary: Check track exists + description: Check track exists + operationId: isTracksExists + parameters: + - name: trackIds + in: query + description: The track ids + required: true + schema: + type: array + items: + type: string + format: UUID + responses: + "200": + description: OK + content: + application/json: + schema: + type: boolean + "404": + description: Not Found + /api/track/search: + get: + tags: + - Track + summary: Search for tracks with given '%' pattern. Returns paginated list + description: Search for tracks with given '%' pattern. Returns paginated list + operationId: searchTrackByPattern + parameters: + - name: trackPattern + in: query + required: true + schema: + type: string + - name: page + in: query + required: false + example: 0 + schema: + type: integer + format: int32 + - name: size + in: query + required: false + example: 15 + schema: + type: integer + format: int32 + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/TrackPage" + "404": + description: Not Found + /api/track/list: + get: + tags: + - Track + summary: Find track by ids + description: Find track by ids + operationId: findTrackByIds + parameters: + - name: trackIds + in: query + description: The track ids + required: true + schema: + type: array + items: + type: string + format: UUID + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Track" + "404": + description: Not Found + /api/vehicle: + get: + tags: + - Vehicle + summary: Read vehicle by query + description: Read Vehicle by query and pageable from device service + operationId: vehicleReadByQuery + parameters: + - name: query + in: query + required: false + description: >- + Comma separated list of `{field}{operation}{value}` where operation can be + `:` for equal, + `!` for not equal and + `~` for like operation + schema: + type: string + - name: search + in: query + required: false + description: Search value to query searchable fields against + schema: + type: string + - name: page + in: query + required: false + example: 0 + schema: + type: integer + format: int32 + - name: size + in: query + required: false + example: 15 + schema: + type: integer + format: int32 + - name: sort + in: query + required: false + example: + - name:asc + schema: + type: array + items: + type: string + example: name:asc + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/VehiclePage" + "404": + description: Not Found + /api/vehicle/{vin}: + get: + tags: + - Vehicle + summary: Find vehicle by vin + description: Find vehicle by vin + operationId: getVehicleByVin + parameters: + - name: vin + in: path + description: The vehicle vin + required: true + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/VehicleResponse" + "404": + description: Not Found + /api/track/hardware: + get: + tags: + - Track + summary: Read Hardware Module + description: Read Hardware Module + operationId: getHardwareModule + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + type: String + "404": + description: Not Found +components: + schemas: + TrackPage: + type: object + properties: + content: + type: array + items: + $ref: '#/components/schemas/Track' + empty: + type: boolean + first: + type: boolean + last: + type: boolean + page: + type: integer + format: int32 + size: + type: integer + format: int32 + pages: + type: integer + format: int32 + elements: + type: integer + format: int32 + total: + type: integer + format: int64 + description: The Track page data + Track: + type: object + properties: + id: + type: string + format: uuid + name: + type: string + trackType: + type: string + state: + type: string + duration: + type: string + description: + type: string + vehicles: + type: array + items: + $ref: "#/components/schemas/VehicleResponse" + description: Track data + VehicleResponse: + type: object + properties: + vin: + type: string + owner: + type: string + ecomDate: + type: string + country: + type: string + model: + type: string + brand: + type: string + region: + type: string + createdAt: + type: string + instantiatedAt: + type: string + updatedAt: + type: string + status: + type: string + type: + type: string + fleets: + type: array + items: + $ref: "#/components/schemas/FleetResponse" + devices: + type: array + items: + $ref: "#/components/schemas/DeviceResponse" + services: + type: array + items: + $ref: "#/components/schemas/ServiceResponse" + tags: + type: array + items: + type: string + description: vehicle data + FleetResponse: + type: object + properties: + id: + type: string + name: + type: string + type: + type: string + description: Fleet data + ServiceResponse: + type: object + properties: + serviceId: + type: string + operation: + type: string + updatedAt: + type: string + description: Fleet data + DeviceResponse: + type: object + properties: + id: + type: string + type: + type: string + status: + type: string + createdAt: + type: string + gatewayId: + type: string + modelType: + type: string + dmProtocol: + type: string + modifiedAt: + type: string + dmProtocolVersion: + type: string + serialNumber: + type: string + components: + type: array + items: + $ref: "#/components/schemas/DeviceComponentResponse" + description: device data + DeviceComponentResponse: + type: object + properties: + id: + type: string + name: + type: string + status: + type: string + version: + type: string + environmentType: + type: string + description: device component data + TrackInput: + type: object + properties: + name: + type: string + trackType: + type: string + state: + type: string + duration: + type: string + description: + type: string + vehicles: + type: array + items: + $ref: "#/components/schemas/Vehicle" + description: create track request data + Vehicle: + type: object + properties: + vin: + type: string + country: + type: string + description: vehicle data for track creation + VehiclePage: + type: object + properties: + content: + type: array + items: + $ref: '#/components/schemas/VehicleResponse' + empty: + type: boolean + first: + type: boolean + last: + type: boolean + page: + type: integer + format: int32 + size: + type: integer + format: int32 + pages: + type: integer + format: int32 + elements: + type: integer + format: int32 + total: + type: integer + format: int64 + description: The Vehicle page data diff --git a/tracks-management-service/api/pom.xml b/tracks-management-service/api/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..8f4e3d69c655966f6e0d01d3987e8114d66d574b --- /dev/null +++ b/tracks-management-service/api/pom.xml @@ -0,0 +1,84 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <artifactId>tracks-management-service</artifactId> + <groupId>com.tsystems.dco</groupId> + <version>latest</version> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>tracks-management-service-api</artifactId> + <packaging>jar</packaging> + + <properties> + <cyclonedx.skip>true</cyclonedx.skip> + <dependency-track.skip>true</dependency-track.skip> + </properties> + + <dependencies> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-web</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.springdoc</groupId> + <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> + <version>2.1.0</version> + </dependency> + <dependency> + <groupId>org.springdoc</groupId> + <artifactId>springdoc-openapi-ui</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.openapitools</groupId> + <artifactId>openapi-generator-maven-plugin</artifactId> + <executions> + <execution> + <id>openapi-track</id> + <goals> + <goal>generate</goal> + </goals> + <configuration> + <inputSpec>${project.basedir}/openapi/openapi-track.yml</inputSpec> + <generatorName>spring</generatorName> + <packageName>com.tsystems.dco</packageName> + <invokerPackage>com.tsystems.dco</invokerPackage> + <apiPackage>com.tsystems.dco.api</apiPackage> + <modelPackage>com.tsystems.dco.model</modelPackage> + <generateSupportingFiles>false</generateSupportingFiles> + <typeMappings> + <typeMapping>OffsetDateTime=Instant</typeMapping> + </typeMappings> + <importMappings> + <importMapping>java.time.OffsetDateTime=java.time.Instant</importMapping> + </importMappings> + <configOptions> + <useTags>true</useTags> + <interfaceOnly>true</interfaceOnly> + <useSpringBoot3>true</useSpringBoot3> + <serializableModel>true</serializableModel> + <skipDefaultInterface>true</skipDefaultInterface> + <hideGenerationTimestamp>true</hideGenerationTimestamp> + <openApiNullable>false</openApiNullable> + <additionalModelTypeAnnotations>@lombok.Builder @lombok.NoArgsConstructor @lombok.AllArgsConstructor</additionalModelTypeAnnotations> + </configOptions> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + +</project> diff --git a/tracks-management-service/app-database/pom.xml b/tracks-management-service/app-database/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..75a5674044abab3eb51a844f487ca2dfc9500fe2 --- /dev/null +++ b/tracks-management-service/app-database/pom.xml @@ -0,0 +1,95 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <artifactId>tracks-management-service</artifactId> + <groupId>com.tsystems.dco</groupId> + <version>latest</version> + </parent> + <modelVersion>4.0.0</modelVersion> + <packaging>jar</packaging> + <properties> + <dependency-track.skip>true</dependency-track.skip> + <sonar.coverage.exclusions> + **/AppDatabase.java + </sonar.coverage.exclusions> + </properties> + + <artifactId>tracks-management-service-app-database</artifactId> + + <dependencies> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-data-jpa</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-configuration-processor</artifactId> + <optional>true</optional> + </dependency> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + </dependency> + <dependency> + <groupId>org.liquibase</groupId> + <artifactId>liquibase-core</artifactId> + </dependency> + <dependency> + <groupId>org.postgresql</groupId> + <artifactId>postgresql</artifactId> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.module</groupId> + <artifactId>jackson-module-jaxb-annotations</artifactId> + </dependency> + <!-- test --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-test</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <resources> + <resource> + <directory>src/main/resources</directory> + <filtering>true</filtering> + </resource> + </resources> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <annotationProcessorPaths> + <path> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + <version>1.18.24</version> + </path> + <path> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-configuration-processor</artifactId> + <version>2.7.4</version> + </path> + </annotationProcessorPaths> + </configuration> + </plugin> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + <executions> + <execution> + <goals> + <goal>repackage</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project> diff --git a/tracks-management-service/app-database/src/main/java/com/tsystems/dco/AppDatabase.java b/tracks-management-service/app-database/src/main/java/com/tsystems/dco/AppDatabase.java new file mode 100644 index 0000000000000000000000000000000000000000..8bc702a4e87540161049b43dcdf27bc801467cf3 --- /dev/null +++ b/tracks-management-service/app-database/src/main/java/com/tsystems/dco/AppDatabase.java @@ -0,0 +1,26 @@ +package com.tsystems.dco; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; + +/** + * Entrypoint of application for database migrations. + */ +@Slf4j +@EnableConfigurationProperties +@SpringBootApplication +public class AppDatabase { + + /** + * Main application entrypoint. + * + * @param args command line arguments + */ + public static void main(String[] args) { + var ctx = SpringApplication.run(AppDatabase.class, args); + var code = SpringApplication.exit(ctx); + System.exit(code); + } +} diff --git a/tracks-management-service/app-database/src/main/java/com/tsystems/dco/AppDatabaseProperties.java b/tracks-management-service/app-database/src/main/java/com/tsystems/dco/AppDatabaseProperties.java new file mode 100644 index 0000000000000000000000000000000000000000..70f1b7adc3e59afababb035cd2b1a13a178c24a6 --- /dev/null +++ b/tracks-management-service/app-database/src/main/java/com/tsystems/dco/AppDatabaseProperties.java @@ -0,0 +1,66 @@ +package com.tsystems.dco; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; +import org.springframework.context.annotation.Configuration; + +/** + * Properties of application for database migrations. + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Configuration +@ConfigurationProperties(prefix = "app") +public class AppDatabaseProperties { + + /** + * The app name. + */ + private String name; + /** + * The app version. + */ + private String version; + /** + * The postgres properties. + */ + @NestedConfigurationProperty + private Postgres postgres; + + /** + * Properties of postgres. + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Postgres { + + /** + * The postgres host. + */ + private String host; + /** + * The postgres port. + */ + private Integer port; + /** + * The postgres database. + */ + private String database; + /** + * The postgres username. + */ + private String username; + /** + * The postgres password. + */ + private String password; + } +} diff --git a/tracks-management-service/app-database/src/main/resources/application.yml b/tracks-management-service/app-database/src/main/resources/application.yml new file mode 100644 index 0000000000000000000000000000000000000000..18b767fcfd29728c07ee673d836fe993f4e7afae --- /dev/null +++ b/tracks-management-service/app-database/src/main/resources/application.yml @@ -0,0 +1,22 @@ +app: + postgres: + host: localhost + port: 5432 + database: postgres + username: postgres + password: postgres +spring: + main: + web-application-type: none + datasource: + driver-class-name: org.postgresql.Driver + url: jdbc:postgresql://${app.postgres.host}:${app.postgres.port}/${app.postgres.database} + username: ${app.postgres.username} + password: ${app.postgres.password} + jpa: + database-platform: org.hibernate.dialect.PostgreSQLDialect + hibernate: + ddl-auto: validate + liquibase: + enabled: true + change-log: classpath:db/changelog.yml diff --git a/tracks-management-service/app-database/src/main/resources/db/changelog.yml b/tracks-management-service/app-database/src/main/resources/db/changelog.yml new file mode 100644 index 0000000000000000000000000000000000000000..c79d7aa8d9365f820865b2fb57e1225394f94fc1 --- /dev/null +++ b/tracks-management-service/app-database/src/main/resources/db/changelog.yml @@ -0,0 +1,4 @@ +databaseChangeLog: +- include: + file: changelog/v000-track-schema.sql + relativeToChangelogFile: true diff --git a/tracks-management-service/app-database/src/main/resources/db/changelog/v000-track-schema.sql b/tracks-management-service/app-database/src/main/resources/db/changelog/v000-track-schema.sql new file mode 100644 index 0000000000000000000000000000000000000000..812f031fcf3b012135381bb1849bcfb6f7dca53a --- /dev/null +++ b/tracks-management-service/app-database/src/main/resources/db/changelog/v000-track-schema.sql @@ -0,0 +1,4 @@ +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; +CREATE TABLE IF NOT EXISTS track (id uuid DEFAULT uuid_generate_v4 () not null, created_at timestamp, description varchar(255), duration varchar(255), name varchar(255), state varchar(255), track_type varchar(255), primary key (id)); +CREATE TABLE IF NOT EXISTS vehicle (id uuid DEFAULT uuid_generate_v4 () not null, country varchar(255), vin varchar(255), track_id uuid, primary key (id)); +ALTER TABLE vehicle add constraint fk_track_id foreign key (track_id) references track; diff --git a/tracks-management-service/app/pom.xml b/tracks-management-service/app/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..e817399633e082d1f66bb1ae9035f99fd5d4a1b1 --- /dev/null +++ b/tracks-management-service/app/pom.xml @@ -0,0 +1,188 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <artifactId>tracks-management-service</artifactId> + <groupId>com.tsystems.dco</groupId> + <version>latest</version> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>tracks-management-service-app</artifactId> + <properties> + <cyclonedx.skip>true</cyclonedx.skip> + <dependency-track.skip>true</dependency-track.skip> + <sonar.coverage.exclusions> + **/App.java, + **/src/main/java/com/tsystems/dco/*/entity/**, + **/src/main/java/com/tsystems/dco/AppProperties** + </sonar.coverage.exclusions> + </properties> + + <dependencies> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-web</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-configuration-processor</artifactId> + <optional>true</optional> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-actuator</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-starter-openfeign</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-validation</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-security</artifactId> + </dependency> + <dependency> + <groupId>org.mapstruct</groupId> + <artifactId>mapstruct</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-data-jpa</artifactId> + </dependency> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + </dependency> + <!-- CVE-2022-38752 --> + <dependency> + <groupId>org.yaml</groupId> + <artifactId>snakeyaml</artifactId> + <version>1.32</version> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>tracks-management-service-api</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>tracks-management-service-app-database</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.module</groupId> + <artifactId>jackson-module-jaxb-annotations</artifactId> + </dependency> + <dependency> + <groupId>org.springdoc</groupId> + <artifactId>springdoc-openapi-ui</artifactId> + </dependency> + <dependency> + <groupId>org.postgresql</groupId> + <artifactId>postgresql</artifactId> + <scope>runtime</scope> + </dependency> + <!-- Minio storage / AWS S3 --> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>s3</artifactId> + </dependency> + <!-- test --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-test</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <resources> + <resource> + <directory>src/main/resources</directory> + <filtering>true</filtering> + </resource> + </resources> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-resources-plugin</artifactId> + <executions> + <execution> + <id>resources</id> + <phase>validate</phase> + <goals> + <goal>resources</goal> + </goals> + <configuration> + <delimiters> + <delimiter>@</delimiter> + </delimiters> + <useDefaultDelimiters>false</useDefaultDelimiters> + </configuration> + </execution> + <!-- copy liquibase sets from app-database to app for possibility to enable liquibase on app also --> + <execution> + <id>copy-resources-app-database</id> + <phase>validate</phase> + <goals> + <goal>copy-resources</goal> + </goals> + <configuration> + <outputDirectory>${project.basedir}/src/main/resources/db</outputDirectory> + <resources> + <resource> + <directory>${project.basedir}/../app-database/src/main/resources/db</directory> + </resource> + </resources> + </configuration> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <annotationProcessorPaths> + <path> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + <version>1.18.24</version> + </path> + <path> + <groupId>org.mapstruct</groupId> + <artifactId>mapstruct-processor</artifactId> + <version>1.4.2.Final</version> + </path> + <path> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-configuration-processor</artifactId> + <version>2.7.4</version> + </path> + </annotationProcessorPaths> + </configuration> + </plugin> + <plugin> + <groupId>org.sonarsource.scanner.maven</groupId> + <artifactId>sonar-maven-plugin</artifactId> + <version>3.9.1.2184</version> + </plugin> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + <executions> + <execution> + <goals> + <goal>repackage</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + +</project> diff --git a/tracks-management-service/app/src/main/java/com/tsystems/dco/App.java b/tracks-management-service/app/src/main/java/com/tsystems/dco/App.java new file mode 100644 index 0000000000000000000000000000000000000000..0683531791c793014ab23640bad1fd839b68ed81 --- /dev/null +++ b/tracks-management-service/app/src/main/java/com/tsystems/dco/App.java @@ -0,0 +1,52 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +@EnableConfigurationProperties +@EnableJpaAuditing +@EnableJpaRepositories +@EnableFeignClients +@SpringBootApplication +public class App { + private static final Logger logger = LoggerFactory.getLogger(App.class); + /** + * Main application entry point. + * + * @param args command line arguments + */ + public static void main(String[] args) { + SpringApplication.run(App.class, args); + logger.info("******Application Started******"); + } + +} diff --git a/tracks-management-service/app/src/main/java/com/tsystems/dco/AppProperties.java b/tracks-management-service/app/src/main/java/com/tsystems/dco/AppProperties.java new file mode 100644 index 0000000000000000000000000000000000000000..48e787602d35d9876f6eebf1772be265850fc13e --- /dev/null +++ b/tracks-management-service/app/src/main/java/com/tsystems/dco/AppProperties.java @@ -0,0 +1,142 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; +import org.springframework.context.annotation.Configuration; + +/** + * Properties of application. + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Configuration +@ConfigurationProperties(prefix = "app") +public class AppProperties { + + /** + * The app name. + */ + private String name; + /** + * The app version. + */ + private String version; + /** + * The rest properties. + */ + @NestedConfigurationProperty + private Rest rest; + /** + * The probes properties. + */ + @NestedConfigurationProperty + private Probes probes; + /** + * The cors properties. + */ + @NestedConfigurationProperty + private Cors cors; + + /** + * The postgres properties. + */ + @NestedConfigurationProperty + private Postgres postgres; + + /** + * Properties of rest. + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Rest { + + private Integer port; + } + + /** + * Properties of probes. + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Probes { + + private Integer port; + } + + /** + * Properties of cors. + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Cors { + + private String headers; + private String origins; + } + + /** + * Properties of postgres. + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Postgres { + + /** + * The postgres host. + */ + private String host; + /** + * The postgres port. + */ + private Integer port; + /** + * The postgres database. + */ + private String database; + /** + * The postgres username. + */ + private String username; + /** + * The postgres password. + */ + private String password; + } +} diff --git a/tracks-management-service/app/src/main/java/com/tsystems/dco/config/SecurityConfig.java b/tracks-management-service/app/src/main/java/com/tsystems/dco/config/SecurityConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..e16bdf2df442be9126ab563b1c3626031ca224dc --- /dev/null +++ b/tracks-management-service/app/src/main/java/com/tsystems/dco/config/SecurityConfig.java @@ -0,0 +1,81 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.web.SecurityFilterChain; + +import static org.springframework.security.config.Customizer.withDefaults; + +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + @Value("${app.username}") + private String username; + @Value("${app.password}") + private String password; + + @Bean + public SecurityFilterChain configure(HttpSecurity http) throws Exception { + return http.csrf(csrf -> csrf.disable()) + .authorizeRequests(auth -> auth.anyRequest().authenticated()) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .httpBasic(withDefaults()) + .build(); + } + + @Bean + public InMemoryUserDetailsManager userDetailsManager() { + + UserDetails user1 = User.withDefaultPasswordEncoder() + .username(username) + .password(password) + .roles("USER") + .build(); + + UserDetails user2 = User.withDefaultPasswordEncoder() + .username("dco") + .password("dco") + .roles("USER") + .build(); + + UserDetails admin = User.withDefaultPasswordEncoder() + .username("admin") + .password("password") + .roles("USER","ADMIN") + .build(); + + return new InMemoryUserDetailsManager(user1, user2, admin); + } + +} diff --git a/tracks-management-service/app/src/main/java/com/tsystems/dco/config/WebConfig.java b/tracks-management-service/app/src/main/java/com/tsystems/dco/config/WebConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..f1a8e9ad383b6ddbb926fdaacd1f12db93ee41c2 --- /dev/null +++ b/tracks-management-service/app/src/main/java/com/tsystems/dco/config/WebConfig.java @@ -0,0 +1,48 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.config; + +import com.tsystems.dco.AppProperties; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * Configuration for MVC and cors. + */ +@Configuration +@RequiredArgsConstructor +public class WebConfig implements WebMvcConfigurer { + + private final AppProperties properties; + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedMethods("HEAD", "GET", "PUT", "POST", "DELETE", "PATCH", "OPTIONS") + .allowedOrigins(properties.getCors().getOrigins()) + .allowedHeaders(properties.getCors().getHeaders()); + } +} diff --git a/tracks-management-service/app/src/main/java/com/tsystems/dco/exception/BaseException.java b/tracks-management-service/app/src/main/java/com/tsystems/dco/exception/BaseException.java new file mode 100644 index 0000000000000000000000000000000000000000..b01682e7cf58cb17e6792a1b118b1eaf322d8751 --- /dev/null +++ b/tracks-management-service/app/src/main/java/com/tsystems/dco/exception/BaseException.java @@ -0,0 +1,49 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.exception; + +import lombok.Getter; +import lombok.ToString; +import org.springframework.http.HttpStatus; + +/** + * Base exception that contains a http status code for {@link BaseExceptionHandler}. + */ +@Getter +@ToString +public class BaseException extends RuntimeException { + + private final HttpStatus status; + + /** + * Base exception constructor with status and message. + * + * @param status The http status that should occur when exception is thrown + * @param message The message that should occur when exception is thrown + */ + public BaseException(HttpStatus status, String message) { + super(message); + this.status = status; + } +} diff --git a/tracks-management-service/app/src/main/java/com/tsystems/dco/exception/BaseExceptionHandler.java b/tracks-management-service/app/src/main/java/com/tsystems/dco/exception/BaseExceptionHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..590c87a6cbbbde8bbc71abb0590cf7183fd54a00 --- /dev/null +++ b/tracks-management-service/app/src/main/java/com/tsystems/dco/exception/BaseExceptionHandler.java @@ -0,0 +1,126 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.exception; + +import lombok.Generated; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.BindException; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Base exception handler that returns a generic map as response to user on exception. + */ +@Slf4j +@Generated +@RestControllerAdvice +public class BaseExceptionHandler { + + private static final String KEY_STATUS = "status"; + private static final String KEY_MESSAGE = "message"; + + /** + * Default exception handler. + * + * @param exception the exception with message + * @return the response with status internal server error + */ + @ExceptionHandler(Exception.class) + public ResponseEntity<Map<String, Object>> handleExceptions(Exception exception) { + log.warn("Exception occurred. {}", exception.getMessage()); + log.warn("Exception content.", exception); + var status = HttpStatus.INTERNAL_SERVER_ERROR; + var body = new HashMap<String, Object>(); + body.put(KEY_STATUS, status); + body.put(KEY_MESSAGE, exception.getMessage()); + return ResponseEntity.status(status).body(body); + } + + /** + * Validation exception handler. + * + * @param exception the exception with message + * @return the response with status bad request + */ + @ExceptionHandler({ + BindException.class, + MethodArgumentNotValidException.class + }) + public ResponseEntity<Map<String, Object>> handleValidationExceptions(Exception exception) { + log.warn("Validation exception occurred. {}", exception.getMessage()); + var status = HttpStatus.BAD_REQUEST; + var body = new HashMap<String, Object>(); + body.put(KEY_STATUS, status); + body.put(KEY_MESSAGE, exception instanceof BindException + ? ((BindException) exception).getBindingResult().getAllErrors().stream() + .map(error -> { + var field = error.getCode(); + if (error instanceof FieldError fieldError) { + field = fieldError.getField(); + } + return String.format("%s %s", field, error.getDefaultMessage()); + }) + .collect(Collectors.joining(", ")) + : exception.getMessage()); + return ResponseEntity.status(status).body(body); + } + + /** + * DataNotFoundException handler. + * + * @param exception the base exception with message + * @return the response with status of base exception + */ + @ExceptionHandler(DataNotFoundException.class) + public ResponseEntity<Map<String, Object>> handleDataNotFoundException(DataNotFoundException exception) { + log.warn("DataNotFound exception occurred. {}", exception.getMessage()); + var body = new HashMap<String, Object>(); + body.put(KEY_STATUS, exception.getStatus()); + body.put(KEY_MESSAGE, exception.getMessage()); + return ResponseEntity.status(exception.getStatus()).body(body); + } + + /** + * DataDeletionException handler. + * + * @param exception the base exception with message + * @return the response with status of base exception + */ + @ExceptionHandler(DataDeletionException.class) + public ResponseEntity<Map<String, Object>> handleDataDeletionException(DataDeletionException exception) { + log.warn("DataDeletion exception occurred. {}", exception.getMessage()); + var body = new HashMap<String, Object>(); + body.put(KEY_STATUS, exception.getStatus()); + body.put(KEY_MESSAGE, exception.getMessage()); + return ResponseEntity.status(exception.getStatus()).body(body); + } +} diff --git a/tracks-management-service/app/src/main/java/com/tsystems/dco/exception/DataDeletionException.java b/tracks-management-service/app/src/main/java/com/tsystems/dco/exception/DataDeletionException.java new file mode 100644 index 0000000000000000000000000000000000000000..926f68609ec0a8d731f03a8e8389ce82ec957e7f --- /dev/null +++ b/tracks-management-service/app/src/main/java/com/tsystems/dco/exception/DataDeletionException.java @@ -0,0 +1,48 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.exception; + +import lombok.Generated; +import lombok.Getter; +import lombok.ToString; +import org.springframework.http.HttpStatus; + +@Getter +@ToString +@Generated +public class DataDeletionException extends RuntimeException { + + private final HttpStatus status; + + /** + * DataDeletionException constructor with status and message. + * + * @param status The http status that should occur when exception is thrown + * @param message The message that should occur when exception is thrown + */ + public DataDeletionException(HttpStatus status, String message) { + super(message); + this.status = status; + } +} diff --git a/tracks-management-service/app/src/main/java/com/tsystems/dco/exception/DataNotFoundException.java b/tracks-management-service/app/src/main/java/com/tsystems/dco/exception/DataNotFoundException.java new file mode 100644 index 0000000000000000000000000000000000000000..2c365f43fe58f6b43e6d444b5f9012a1026619c7 --- /dev/null +++ b/tracks-management-service/app/src/main/java/com/tsystems/dco/exception/DataNotFoundException.java @@ -0,0 +1,50 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.exception; + +import lombok.Generated; +import lombok.Getter; +import lombok.ToString; +import org.springframework.http.HttpStatus; + +/** + * DataNotFoundException that contains a http status code for {@link BaseExceptionHandler}. + */ +@Getter +@ToString +@Generated +public class DataNotFoundException extends RuntimeException { + private final HttpStatus status; + + /** + * DataNotFoundException constructor with status and message. + * + * @param status The http status that should occur when exception is thrown + * @param message The message that should occur when exception is thrown + */ + public DataNotFoundException(HttpStatus status, String message) { + super(message); + this.status = status; + } +} diff --git a/tracks-management-service/app/src/main/java/com/tsystems/dco/integration/FeignClientConfiguration.java b/tracks-management-service/app/src/main/java/com/tsystems/dco/integration/FeignClientConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..5ac8e04684dd6a6a03286f9906d474518bc0d6b1 --- /dev/null +++ b/tracks-management-service/app/src/main/java/com/tsystems/dco/integration/FeignClientConfiguration.java @@ -0,0 +1,44 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + + +package com.tsystems.dco.integration; + +import feign.auth.BasicAuthRequestInterceptor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class FeignClientConfiguration { + + @Value("${app.username}") + private String username; + @Value("${app.password}") + private String password; + + @Bean + public BasicAuthRequestInterceptor basicAuthRequestInterceptor() { + return new BasicAuthRequestInterceptor(username, password); + } +} diff --git a/tracks-management-service/app/src/main/java/com/tsystems/dco/integration/ScenarioApiClient.java b/tracks-management-service/app/src/main/java/com/tsystems/dco/integration/ScenarioApiClient.java new file mode 100644 index 0000000000000000000000000000000000000000..3664e2a038a2027d0acf1ff1477bf8b0fdd1ba9b --- /dev/null +++ b/tracks-management-service/app/src/main/java/com/tsystems/dco/integration/ScenarioApiClient.java @@ -0,0 +1,41 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.integration; + + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.UUID; + +@FeignClient(name = "ScenarioApiClient", + url = "${scenario-service.url}", + configuration = FeignClientConfiguration.class) +public interface ScenarioApiClient { + + @GetMapping(value = "/api/simulation/track", produces = {"application/json"}) + ResponseEntity<Boolean> isTrackAssociatedWithSimulation(@RequestParam(value = "id", required = true) UUID id); +} diff --git a/tracks-management-service/app/src/main/java/com/tsystems/dco/mapper/TrackMapper.java b/tracks-management-service/app/src/main/java/com/tsystems/dco/mapper/TrackMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..55e102dcf0759572e86128f820fbacf91e4732b6 --- /dev/null +++ b/tracks-management-service/app/src/main/java/com/tsystems/dco/mapper/TrackMapper.java @@ -0,0 +1,45 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.mapper; + +import com.tsystems.dco.track.entity.TrackEntity; +import com.tsystems.dco.model.Track; +import com.tsystems.dco.model.TrackInput; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; +import java.util.List; + +@Mapper +public interface TrackMapper { + TrackMapper INSTANCE = Mappers.getMapper(TrackMapper.class); + + Track toModel(TrackEntity entity); + + List<Track> toModel(List<TrackEntity> entities); + + TrackEntity toEntity(TrackInput trackInput); + + TrackEntity toEntity(Track track); +} + diff --git a/tracks-management-service/app/src/main/java/com/tsystems/dco/track/controller/TrackController.java b/tracks-management-service/app/src/main/java/com/tsystems/dco/track/controller/TrackController.java new file mode 100644 index 0000000000000000000000000000000000000000..7ac9b479f5b66e4174eb4148ea1b83fd38430b8f --- /dev/null +++ b/tracks-management-service/app/src/main/java/com/tsystems/dco/track/controller/TrackController.java @@ -0,0 +1,183 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.track.controller; + +import com.tsystems.dco.model.Track; +import com.tsystems.dco.model.TrackInput; +import com.tsystems.dco.model.TrackPage; +import com.tsystems.dco.track.service.TrackService; +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; +import com.tsystems.dco.api.TrackApi; + +import java.util.List; +import java.util.UUID; + +@RestController +@RequiredArgsConstructor +public class TrackController implements TrackApi { + + private static final Logger LOGGER = LoggerFactory.getLogger(TrackController.class); + + private final TrackService trackService; + + /** + * POST /api/track : Create a Track + * Create a Track to database + * + * @param trackInput (required) + * @return OK (status code 200) + * or Bad Request (status code 400) + * or Not Found (status code 404) + */ + @Override + public ResponseEntity<Track> createTrack(TrackInput trackInput) { + LOGGER.info("creating track : {}", trackInput.getName()); + return ResponseEntity + .status(HttpStatus.CREATED) + .body(trackService.createTrack(trackInput)); + } + + /** + * DELETE /api/track : Delete a track by id + * Delete a track by id from database + * + * @param id The track id (required) + * @return OK (status code 200) + * or Bad Request (status code 400) + * or Not Found (status code 404) + */ + @Override + public ResponseEntity<String> deleteTrackById(UUID id) { + LOGGER.info("Deleting track : {}", id); + trackService.deleteTrackById(id); + return ResponseEntity + .status(HttpStatus.NO_CONTENT) + .build(); + } + + /** + * GET /api/track/{id} : Find track by id + * Find track by id + * + * @param id The track id (required) + * @return OK (status code 200) + * or Not Found (status code 404) + */ + @Override + public ResponseEntity<Track> findTrackById(UUID id) { + LOGGER.info("find track by id : {}", id); + return ResponseEntity + .status(HttpStatus.OK) + .body(trackService.findTrackById(id)); + } + + /** + * GET /api/track/ids : Find track by ids + * Find track by ids + * + * @param trackIds The track ids (required) + * @return OK (status code 200) + * or Not Found (status code 404) + */ + @Override + public ResponseEntity<List<Track>> findTrackByIds(List<UUID> trackIds) { + LOGGER.info("find Tracks By Ids list : {}", trackIds); + return ResponseEntity + .status(HttpStatus.OK) + .body(trackService.findTrackByIds(trackIds)); + } + + /** + * GET /api/track/hardware : Read Hardware Module + * Read Hardware Module + * + * @return OK (status code 200) + * or Not Found (status code 404) + */ + @Override + public ResponseEntity<List<String>> getHardwareModule() { + LOGGER.info("get hardware module list"); + return ResponseEntity + .status(HttpStatus.OK) + .body(trackService.getHardwareModule()); + } + + /** + * GET /api/track/validate : Check track exists + * Check track exists + * + * @param trackIds The track ids (required) + * @return OK (status code 200) + * or Not Found (status code 404) + */ + @Override + public ResponseEntity<Boolean> isTracksExists(List<UUID> trackIds) { + return ResponseEntity + .status(HttpStatus.OK) + .body(trackService.isTracksExists(trackIds)); + } + + /** + * GET /api/track/search : Search for tracks with given '%' pattern. Returns paginated list + * Search for tracks with given '%' pattern. Returns paginated list + * + * @param trackPattern (required) + * @param page (optional) + * @param size (optional) + * @return OK (status code 200) + * or Not Found (status code 404) + */ + @Override + public ResponseEntity<TrackPage> searchTrackByPattern(String trackPattern, Integer page, Integer size) { + LOGGER.info("searching tracks by pattern : {}", trackPattern); + return ResponseEntity + .status(HttpStatus.OK) + .body(trackService.searchTrackByPattern(trackPattern, page, size)); + } + + /** + * GET /api/track : Read Track by query + * Read Track by query and pageable from database + * + * @param query Comma separated list of `{field}{operation}{value}` where operation can be `:` for equal, `!` for not equal and `~` for like operation (optional) + * @param search Search value to query searchable fields agains (optional) + * @param page (optional) + * @param size (optional) + * @param sort (optional) + * @return OK (status code 200) + * or Not Found (status code 404) + */ + @Override + public ResponseEntity<TrackPage> trackReadByQuery(String query, String search, Integer page, Integer size, List<String> sort) { + LOGGER.info("fetching list of tracks with size : {}", size); + return ResponseEntity + .status(HttpStatus.OK) + .body(trackService.readTrackByQuery(query, search, page, size, sort)); + } +} diff --git a/tracks-management-service/app/src/main/java/com/tsystems/dco/track/entity/TrackEntity.java b/tracks-management-service/app/src/main/java/com/tsystems/dco/track/entity/TrackEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..10471f322ab865903016e59a419258aab760ecae --- /dev/null +++ b/tracks-management-service/app/src/main/java/com/tsystems/dco/track/entity/TrackEntity.java @@ -0,0 +1,74 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.track.entity; + +import com.tsystems.dco.vehicle.entity.VehicleEntity; +import lombok.*; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import jakarta.persistence.*; +import java.time.Instant; +import java.util.List; +import java.util.UUID; + +@Builder(toBuilder = true) +@Data +@NoArgsConstructor +@AllArgsConstructor +@EntityListeners({ + AuditingEntityListener.class +}) +@Entity(name = "track") +public class TrackEntity { + private static final long serialVersionUID = -4602210571656981182L; + @Id + @GeneratedValue + @Column(name = "id") + private UUID id; + + @Column(name = "name") + private String name; + + @Column(name = "track_type") + private String trackType; + + @Column(name = "state") + private String state; + + @Column(name = "duration") + private String duration; + + @Column(name = "description") + private String description; + + @LastModifiedDate + @Column(name = "created_at") + private Instant createdAt; + + @OneToMany(targetEntity = VehicleEntity.class, cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @JoinColumn(name = "track_id", referencedColumnName = "id") + private List<VehicleEntity> vehicles; + +} diff --git a/tracks-management-service/app/src/main/java/com/tsystems/dco/track/repository/TrackRepository.java b/tracks-management-service/app/src/main/java/com/tsystems/dco/track/repository/TrackRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..f43d9790273f7547e17b99fd8c0bff2f72d7425e --- /dev/null +++ b/tracks-management-service/app/src/main/java/com/tsystems/dco/track/repository/TrackRepository.java @@ -0,0 +1,46 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.track.repository; + +import com.tsystems.dco.track.entity.TrackEntity; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.UUID; + +@Repository +public interface TrackRepository extends JpaRepository<TrackEntity, UUID>, JpaSpecificationExecutor<TrackEntity> { + + @Query(value = "SELECT * FROM track where LOWER(name) like LOWER(?1)", nativeQuery = true) + Page<TrackEntity> findByTrackLike(String trackPattern, Pageable pageable); + + @Query("select t from track t where id in :ids") + List<TrackEntity> findTrackByIds(@Param("ids") List<UUID> trackIds); +} diff --git a/tracks-management-service/app/src/main/java/com/tsystems/dco/track/service/TrackQuery.java b/tracks-management-service/app/src/main/java/com/tsystems/dco/track/service/TrackQuery.java new file mode 100644 index 0000000000000000000000000000000000000000..3a9fe6f6643c25276dd7951e30f05f198fb408a0 --- /dev/null +++ b/tracks-management-service/app/src/main/java/com/tsystems/dco/track/service/TrackQuery.java @@ -0,0 +1,112 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.track.service; + +import com.tsystems.dco.exception.BaseException; +import com.tsystems.dco.track.entity.TrackEntity; +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.http.HttpStatus; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +@Getter +public class TrackQuery { + + public static final Pattern QUERY_PATTERN = Pattern.compile("([\\w.]+?)([:!~])(.*?),"); + + private final List<Query> queries = new ArrayList<>(); + + public TrackQuery(String query) { + var matcher = QUERY_PATTERN.matcher(query + ","); + while (matcher.find()) { + queries.add(new Query( + matcher.group(1), + Operation.from(matcher.group(2).charAt(0)), + matcher.group(3)) + ); + } + } + + public TrackQuery isValid() { + if (queries.isEmpty()) { + throw new BaseException(HttpStatus.BAD_REQUEST, "The query does not match the valid pattern."); + } + return this; + } + + public Specification<TrackEntity> toSpecification() { + if (queries.isEmpty()) { + return null; + } + Specification<TrackEntity> result = queries.get(0); + for (var i = 1; i < queries.size(); i++) { + result = Specification.where(result).and(queries.get(i)); + } + return result; + } + + public enum Operation { + EQUAL, NOT_EQUAL, LIKE; + + public static Operation from(final char input) { + return switch (input) { + case ':' -> EQUAL; + case '!' -> NOT_EQUAL; + case '~' -> LIKE; + default -> null; + }; + } + } + + @Builder + @RequiredArgsConstructor + public static class Query implements Serializable, Specification<TrackEntity> { + private final String field; + private final Operation operation; + private final String value; + + @Override + public Predicate toPredicate( + Root<TrackEntity> root, + CriteriaQuery<?> query, + CriteriaBuilder builder + ) { + return switch (operation) { + case EQUAL -> builder.equal(root.get(field), value); + case NOT_EQUAL -> builder.notEqual(root.get(field), value); + case LIKE -> builder.like(root.get(field), "%" + value + "%"); + }; + } + } +} diff --git a/tracks-management-service/app/src/main/java/com/tsystems/dco/track/service/TrackService.java b/tracks-management-service/app/src/main/java/com/tsystems/dco/track/service/TrackService.java new file mode 100644 index 0000000000000000000000000000000000000000..3c1739c65bf2a12668886fc9e6507e57fa264ce8 --- /dev/null +++ b/tracks-management-service/app/src/main/java/com/tsystems/dco/track/service/TrackService.java @@ -0,0 +1,50 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.track.service; + +import com.tsystems.dco.model.TrackInput; +import com.tsystems.dco.model.Track; +import com.tsystems.dco.model.TrackPage; + +import java.util.List; +import java.util.UUID; + +public interface TrackService { + + Track createTrack(TrackInput trackInput); + + void deleteTrackById(UUID id); + + TrackPage readTrackByQuery(String query, String search, Integer page, Integer size, List<String> sort); + + Track findTrackById(UUID id); + + TrackPage searchTrackByPattern(String trackPattern, Integer page, Integer size); + + List<Track> findTrackByIds(List<UUID> trackIds); + + boolean isTracksExists(List<UUID> trackIds); + + List<String> getHardwareModule(); +} diff --git a/tracks-management-service/app/src/main/java/com/tsystems/dco/track/service/TrackServiceImpl.java b/tracks-management-service/app/src/main/java/com/tsystems/dco/track/service/TrackServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..78c74f3432ae107a7c81c7271e4d8889365b66dc --- /dev/null +++ b/tracks-management-service/app/src/main/java/com/tsystems/dco/track/service/TrackServiceImpl.java @@ -0,0 +1,241 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.track.service; + +import com.tsystems.dco.exception.DataDeletionException; +import com.tsystems.dco.exception.DataNotFoundException; +import com.tsystems.dco.integration.ScenarioApiClient; +import com.tsystems.dco.mapper.TrackMapper; +import com.tsystems.dco.model.*; +import com.tsystems.dco.track.entity.TrackEntity; +import com.tsystems.dco.track.repository.TrackRepository; +import com.tsystems.dco.vehicle.entity.VehicleEntity; +import com.tsystems.dco.vehicle.service.VehicleService; +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.stream.Stream; + +@Service +@RequiredArgsConstructor +public class TrackServiceImpl implements TrackService { + + private static final Logger LOGGER = LoggerFactory.getLogger(TrackServiceImpl.class); + + private static final Sort.Order ORDER_DEFAULT = Sort.Order.asc("name"); + private static final Integer PAGEABLE_DEFAULT_PAGE = 0; + private static final Integer PAGEABLE_DEFAULT_SIZE = 15; + private static final String ERROR_NOT_FOUND_ID = "Track with id %s not found."; + + private final TrackRepository trackRepository; + private final VehicleService vehicleService; + private final ScenarioApiClient scenarioApiClient; + + + /** + * @param trackInput + * @return Track + */ + @Override + public Track createTrack(TrackInput trackInput) { + var trackEntity = TrackMapper.INSTANCE.toEntity(trackInput); + LOGGER.info("saving track {}", trackInput.getName()); + return TrackMapper.INSTANCE.toModel(trackRepository.save(trackEntity)); + } + + + /** + * @param id + */ + @Override + public void deleteTrackById(UUID id) { + final var deleted = trackRepository.findById(id) + .orElseThrow(() -> new DataNotFoundException(HttpStatus.NOT_FOUND, String.format(ERROR_NOT_FOUND_ID, id))); + LOGGER.info("Deleting track {}", id); + boolean isTrackAssociatedWithSimulation = scenarioApiClient.isTrackAssociatedWithSimulation(deleted.getId()).getBody(); + if (!isTrackAssociatedWithSimulation) { + LOGGER.info("Deleting track : {}", deleted.getId()); + trackRepository.delete(deleted); + } else { + LOGGER.error("Track id : {}, can't be deleted as it has an association with simulation", deleted.getId()); + throw new DataDeletionException(HttpStatus.BAD_REQUEST, "Track can't be deleted as it has an association with simulation"); + } + } + + /** + * @param query + * @param search + * @param page + * @param size + * @param sort + * @return TrackPage + */ + @Override + public TrackPage readTrackByQuery(String query, String search, Integer page, Integer size, List<String> sort) { + var orders = Optional.ofNullable(sort) + .map(s -> s.stream() + .map(so -> so.split(":")) + .map(sp -> sp.length >= 2 ? new Sort.Order(Sort.Direction.fromString(sp[1]), sp[0]) : null) + .filter(Objects::nonNull) + ).orElse(Stream.of(ORDER_DEFAULT)) + .toList(); + + var pageable = PageRequest.of( + Optional.ofNullable(page).orElse(PAGEABLE_DEFAULT_PAGE), + Optional.ofNullable(size).orElse(PAGEABLE_DEFAULT_SIZE), + Sort.by(orders) + ); + + var specs = Optional.ofNullable(search) + .map(s -> Specification + .where(TrackQuery.Query.builder() + .field("name").operation(TrackQuery.Operation.LIKE).value(s) + .build()) + .or(TrackQuery.Query.builder() + .field("trackType").operation(TrackQuery.Operation.LIKE).value(s) + .build()) + .or(TrackQuery.Query.builder() + .field("state").operation(TrackQuery.Operation.LIKE).value(s) + .build())) + .orElse(new TrackQuery(query).toSpecification()); + + var queried = trackRepository.findAll(specs, pageable); + return new TrackPage() + .empty(queried.isEmpty()) + .first(queried.isFirst()) + .last(queried.isLast()) + .page(queried.getNumber()) + .size(queried.getSize()) + .pages(queried.getTotalPages()) + .elements(queried.getNumberOfElements()) + .total(queried.getTotalElements()) + .content(TrackMapper.INSTANCE.toModel(queried.getContent())); + } + + + /** + * @param id + * @return Track + */ + @Override + public Track findTrackById(UUID id) { + var trackEntity = trackRepository.findById(id) + .orElseThrow(() -> new DataNotFoundException(HttpStatus.NOT_FOUND, String.format(ERROR_NOT_FOUND_ID, id))); + List<VehicleEntity> vehicleEntities = trackEntity.getVehicles(); + List<VehicleResponse> vehicleResponses = new ArrayList<>(); + vehicleEntities.forEach(entity -> vehicleResponses.add(vehicleService.getVehicleByVin(entity.getVin()))); + return TrackMapper.INSTANCE.toModel(trackEntity).vehicles(vehicleResponses); + } + + + /** + * @param trackPattern + * @param page + * @param size + * @return TrackPage + */ + @Override + public TrackPage searchTrackByPattern(String trackPattern, Integer page, Integer size) { + var pageable = PageRequest.of( + Optional.ofNullable(page).orElse(PAGEABLE_DEFAULT_PAGE), + Optional.ofNullable(size).orElse(PAGEABLE_DEFAULT_SIZE) + ); + Page<TrackEntity> trackPageEntity = trackRepository.findByTrackLike(trackPattern + "%", pageable); + LOGGER.info("track search list - {}", trackPageEntity.getTotalElements()); + List<Track> tracks = TrackMapper.INSTANCE.toModel(trackPageEntity.getContent()); + return TrackPage.builder() + .content(tracks) + .first(trackPageEntity.isFirst()) + .empty(trackPageEntity.isEmpty()) + .last(trackPageEntity.isLast()) + .page(trackPageEntity.getPageable().getPageNumber()) + .size(trackPageEntity.getPageable().getPageSize()) + .elements(trackPageEntity.getSize()) + .total(trackPageEntity.getTotalElements()) + .pages(trackPageEntity.getTotalPages()) + .build(); + } + + + /** + * @param trackIds + * @return List + */ + @Override + public List<Track> findTrackByIds(List<UUID> trackIds) { + List<TrackEntity> tracksList = trackRepository.findTrackByIds(trackIds); + LOGGER.info("track list size - {}", tracksList.size()); + List<Track> tracks = new ArrayList<>(); + tracksList.forEach(track -> { + List<VehicleEntity> vehicleEntities = track.getVehicles(); + List<VehicleResponse> vehicleResponses = new ArrayList<>(); + vehicleEntities.forEach(entity -> vehicleResponses.add(vehicleService.getVehicleByVin(entity.getVin()))); + tracks.add(TrackMapper.INSTANCE.toModel(track).vehicles(vehicleResponses)); + }); + return tracks; + } + + + /** + * @param trackIds + * @return boolean + */ + @Override + public boolean isTracksExists(List<UUID> trackIds) { + trackIds.forEach(track -> { + if (!trackRepository.existsById(track)) + throw new DataNotFoundException(HttpStatus.BAD_REQUEST, "Track doesn't not exist"); + }); + return true; + } + + /** + * @return List + */ + @Override + public List<String> getHardwareModule() { + return Arrays.asList("HARDWARE-OTA-998-1ZW6CQN0", + "HARDWARE-OTA-998-1YYHZQOQ", "HARDWARE-OTA-998-1Y8B1UP3", "HARDWARE-OTA-998-1Y1DRJLH", + "HARDWARE-OTA-998-1XTTN0QT", "HARDWARE-OTA-998-1XD761GW", "HARDWARE-OTA-998-1VU9GNY6", + "HARDWARE-OTA-998-1VIE6J4F", "HARDWARE-OTA-998-1VF3LE6K", "HARDWARE-OTA-998-1VDOU4HA", + "HARDWARE-OTA-998-1V4YXLLB", "HARDWARE-OTA-998-1UQRYVAI", "HARDWARE-OTA-998-1UJ1PW1A", + "HARDWARE-OTA-998-1SAVET2I", "HARDWARE-OTA-998-1S0ZL2IQ", "HARDWARE-OTA-998-1RJ27SNK", + "HARDWARE-OTA-998-1R5VFR0F", "HARDWARE-OTA-998-1QWJMAFN", "HARDWARE-OTA-998-1QHXLH9X", + "HARDWARE-OTA-998-1QCR2YQZ", "HARDWARE-OTA-998-1Q5O1JY7", "HARDWARE-OTA-998-1PZG3A7R", + "HARDWARE-OTA-998-1OQINXV0", "HARDWARE-OTA-998-1O660XQT", "HARDWARE-OTA-998-1O0MFN87", + "HARDWARE-OTA-998-1MTXD5RI", "HARDWARE-OTA-998-1MILV08B", "HARDWARE-OTA-998-1MAL5I50", + "HARDWARE-OTA-998-1LYTG36V", "HARDWARE-OTA-998-1LQJ5NI2", "HARDWARE-OTA-998-1L72BO0C", + "HARDWARE-OTA-998-1L5L9N7G", "HARDWARE-OTA-998-1KT2KU3P", "HARDWARE-OTA-998-1K1XTY9R", + "HARDWARE-OTA-998-1K1S2U46", "HARDWARE-OTA-998-1JMZJ77L", "HARDWARE-OTA-998-1JIMOONV", + "HARDWARE-OTA-998-1JCHU54D", "HARDWARE-OTA-998-1JANYCHT", "HARDWARE-OTA-998-1J0FNH0Z"); + } +} diff --git a/tracks-management-service/app/src/main/java/com/tsystems/dco/vehicle/controller/VehicleController.java b/tracks-management-service/app/src/main/java/com/tsystems/dco/vehicle/controller/VehicleController.java new file mode 100644 index 0000000000000000000000000000000000000000..aec8e3361c6ca119284ffd60d3d63513ed681236 --- /dev/null +++ b/tracks-management-service/app/src/main/java/com/tsystems/dco/vehicle/controller/VehicleController.java @@ -0,0 +1,82 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.vehicle.controller; + +import com.tsystems.dco.model.VehiclePage; +import com.tsystems.dco.model.VehicleResponse; +import com.tsystems.dco.vehicle.service.VehicleService; +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; +import com.tsystems.dco.api.VehicleApi; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +public class VehicleController implements VehicleApi { + + private static final Logger LOGGER = LoggerFactory.getLogger(VehicleController.class); + + private final VehicleService vehicleService; + + /** + * GET /api/vehicle/{vin} : Find vehicle by vin + * Find vehicle by vin + * + * @param vin The vehicle vin (required) + * @return OK (status code 200) + * or Not Found (status code 404) + */ + @Override + public ResponseEntity<VehicleResponse> getVehicleByVin(String vin) { + LOGGER.info("get vehicle by vin {}", vin); + return ResponseEntity + .status(HttpStatus.OK) + .body(vehicleService.getVehicleByVin(vin)); + } + + /** + * GET /api/vehicle : Read vehicle by query + * Read Vehicle by query and pageable from device service + * + * @param query Comma separated list of `{field}{operation}{value}` where operation can be `:` for equal, `!` for not equal and `~` for like operation (optional) + * @param search Search value to query searchable fields against (optional) + * @param page (optional) + * @param size (optional) + * @param sort (optional) + * @return OK (status code 200) + * or Not Found (status code 404) + */ + @Override + public ResponseEntity<VehiclePage> vehicleReadByQuery(String query, String search, Integer page, Integer size, List<String> sort) { + LOGGER.info("fetching list of vehicles"); + return ResponseEntity + .status(HttpStatus.OK) + .body(vehicleService.getVehicles(query, search, page, size, sort)); + } +} diff --git a/tracks-management-service/app/src/main/java/com/tsystems/dco/vehicle/entity/VehicleEntity.java b/tracks-management-service/app/src/main/java/com/tsystems/dco/vehicle/entity/VehicleEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..e4c5043f4cf2f5c0164e7ed6b522fa16fc11e1f7 --- /dev/null +++ b/tracks-management-service/app/src/main/java/com/tsystems/dco/vehicle/entity/VehicleEntity.java @@ -0,0 +1,60 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.vehicle.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import java.io.Serializable; +import java.util.UUID; + +/** + * VehicleEntity + */ +@Builder(toBuilder = true) +@Data +@NoArgsConstructor +@AllArgsConstructor +@Entity(name = "vehicle") +public class VehicleEntity implements Serializable { + + private static final long serialVersionUID = 100476035305772432L; + @Id + @GeneratedValue + @Column(name = "id") + private UUID id; + + @Column(name = "vin") + private String vin; + + @Column(name = "country") + private String country; + +} diff --git a/tracks-management-service/app/src/main/java/com/tsystems/dco/vehicle/service/VehicleService.java b/tracks-management-service/app/src/main/java/com/tsystems/dco/vehicle/service/VehicleService.java new file mode 100644 index 0000000000000000000000000000000000000000..159ff20dbf58b2a1b1908c7e704d79e243b0579a --- /dev/null +++ b/tracks-management-service/app/src/main/java/com/tsystems/dco/vehicle/service/VehicleService.java @@ -0,0 +1,36 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.vehicle.service; + +import java.util.List; + +import com.tsystems.dco.model.VehiclePage; +import com.tsystems.dco.model.VehicleResponse; + +public interface VehicleService { + + VehiclePage getVehicles(String query, String search, Integer page, Integer size, List<String> sort); + + VehicleResponse getVehicleByVin(String vin); +} diff --git a/tracks-management-service/app/src/main/java/com/tsystems/dco/vehicle/service/VehicleServiceImpl.java b/tracks-management-service/app/src/main/java/com/tsystems/dco/vehicle/service/VehicleServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..2819f22f20bf194d1f3f8c47ac62bdc8234419d3 --- /dev/null +++ b/tracks-management-service/app/src/main/java/com/tsystems/dco/vehicle/service/VehicleServiceImpl.java @@ -0,0 +1,98 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.vehicle.service; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.tsystems.dco.exception.DataNotFoundException; +import com.tsystems.dco.model.VehiclePage; +import com.tsystems.dco.model.VehicleResponse; +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.ResourceLoader; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.util.List; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class VehicleServiceImpl implements VehicleService { + + private static final Logger LOGGER = LoggerFactory.getLogger(VehicleServiceImpl.class); + + private final ResourceLoader resourceLoader; + + + /** + * @param query + * @param search + * @param page + * @param size + * @param sort + * @return VehiclePage + */ + @Override + public VehiclePage getVehicles(String query, String search, Integer page, Integer size, List<String> sort) { + //get Vehicle metadata from Device Management Service + return getMockVehicleData(); + } + + + /** + * @param vin + * @return VehicleResponse + */ + @Override + public VehicleResponse getVehicleByVin(String vin) { + VehiclePage vehiclePage = getMockVehicleData(); + List<VehicleResponse> vehicleResponses = vehiclePage.getContent(); + Optional<VehicleResponse> vehicleOptional = vehicleResponses.stream().filter(vehicle -> vehicle.getVin().equals(vin)).findAny(); + if (vehicleOptional.isPresent()) { + return vehicleOptional.get(); + } + throw new DataNotFoundException(HttpStatus.NOT_FOUND, "No Data available with this vin"); + } + + /** + * @return VehiclePage + */ + private VehiclePage getMockVehicleData() { + var resource = resourceLoader.getResource("classpath:/vehicle-metadata.json"); + var mapper = new ObjectMapper(); + VehiclePage vehiclePage; + try { + var inputStream = resource.getInputStream(); + vehiclePage = mapper.readValue(inputStream, new TypeReference<>() { + }); + } catch (IOException e) { + LOGGER.error("Exception encountered while getting vehicle metadata"); + throw new DataNotFoundException(HttpStatus.INTERNAL_SERVER_ERROR, "Exception encountered while getting vehicle metadata"); + } + return vehiclePage; + } +} diff --git a/tracks-management-service/app/src/main/resources/application.yml b/tracks-management-service/app/src/main/resources/application.yml new file mode 100644 index 0000000000000000000000000000000000000000..5d97b44ac0dc2fe8250bdc694f773925758e2050 --- /dev/null +++ b/tracks-management-service/app/src/main/resources/application.yml @@ -0,0 +1,63 @@ +app: + rest: + port: 8081 + probes: + port: 8081 + cors: + origins: "*" + headers: "*" + postgres: + host: localhost + port: 5432 + database: postgres + username: postgres + password: postgres + username: developer + password: password +scenario-service: + url: http://localhost:8082 +server: + port: ${app.rest.port} + forward-headers-strategy: FRAMEWORK +spring: + datasource: + driver-class-name: org.postgresql.Driver + url: jdbc:postgresql://${app.postgres.host}:${app.postgres.port}/${app.postgres.database} + username: ${app.postgres.username} + password: ${app.postgres.password} + jpa: + database-platform: org.hibernate.dialect.PostgreSQLDialect + hibernate: + ddl-auto: none + show-sql: true + liquibase: + enabled: false + change-log: classpath:db/changelog.yml +springdoc: + api-docs: + path: /openapi + swagger-ui: + path: /openapi/ui +management: + server: + port: ${app.probes.port} + info: + env: + enabled: true + build: + enabled: false + endpoint: + info: + enabled: true + health: + enabled: true + metrics: + enabled: true + prometheus: + enabled: true + endpoints: + enabled-by-default: false + web: + base-path: /management + exposure: + include: info,health,metrics,prometheus diff --git a/tracks-management-service/app/src/main/resources/db/changelog.yml b/tracks-management-service/app/src/main/resources/db/changelog.yml new file mode 100644 index 0000000000000000000000000000000000000000..c79d7aa8d9365f820865b2fb57e1225394f94fc1 --- /dev/null +++ b/tracks-management-service/app/src/main/resources/db/changelog.yml @@ -0,0 +1,4 @@ +databaseChangeLog: +- include: + file: changelog/v000-track-schema.sql + relativeToChangelogFile: true diff --git a/tracks-management-service/app/src/main/resources/db/changelog/v000-track-schema.sql b/tracks-management-service/app/src/main/resources/db/changelog/v000-track-schema.sql new file mode 100644 index 0000000000000000000000000000000000000000..812f031fcf3b012135381bb1849bcfb6f7dca53a --- /dev/null +++ b/tracks-management-service/app/src/main/resources/db/changelog/v000-track-schema.sql @@ -0,0 +1,4 @@ +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; +CREATE TABLE IF NOT EXISTS track (id uuid DEFAULT uuid_generate_v4 () not null, created_at timestamp, description varchar(255), duration varchar(255), name varchar(255), state varchar(255), track_type varchar(255), primary key (id)); +CREATE TABLE IF NOT EXISTS vehicle (id uuid DEFAULT uuid_generate_v4 () not null, country varchar(255), vin varchar(255), track_id uuid, primary key (id)); +ALTER TABLE vehicle add constraint fk_track_id foreign key (track_id) references track; diff --git a/tracks-management-service/app/src/main/resources/vehicle-metadata.json b/tracks-management-service/app/src/main/resources/vehicle-metadata.json new file mode 100644 index 0000000000000000000000000000000000000000..3332b46c85f80695270b33a4b191cd4aa4829d04 --- /dev/null +++ b/tracks-management-service/app/src/main/resources/vehicle-metadata.json @@ -0,0 +1,545 @@ +{ + "content": [ + { + "vin": "BBTEST00000000340", + "ecomDate": "2022-12-15T21:30:32.105Z", + "country": "FR", + "model": "A-Class", + "brand": "Mercedes-Benz", + "createdAt": "2022-12-15T21:30:46.443421", + "updatedAt": "2023-02-22T00:58:01.734361", + "status": "DRAFT", + "type": "Virtual Vehicle", + "fleets": [ + { + "id": "256", + "name": "fleet bb", + "type": "STATIC" + }, + { + "id": "31", + "name": "411", + "type": "DYNAMIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "devices": [ + { + "id": "84c32703-982a-4efd-920b-b80977073b33", + "type": "TCU", + "status": "ACTIVE", + "createdAt": "2022-12-15T21:30:46.443445", + "gatewayId": "", + "modelType": "HIGH_EU", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-20T14:09:25.437637", + "dmProtocolVersion": "1.0.2", + "components": [ + { + "id": "747243cc", + "name": "Component", + "status": "ACTIVE", + "version": "24edc320", + "environmentType": "Software" + } + ] + }, + { + "id": "36e5322b-92ce-4fb9-ac25-56d2ed0c4b53", + "type": "IVI", + "status": "DRAFT", + "createdAt": "2022-12-20T14:09:12.829388", + "gatewayId": "", + "modelType": "HIGH_EU", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-20T14:09:25.48871", + "dmProtocolVersion": "1.0.2", + "components": [] + } + ], + "services": [] + }, + { + "vin": "BBTEST00000000341", + "ecomDate": "2022-12-16T12:06:09.865Z", + "country": "FR", + "model": "A-Class", + "brand": "Mercedes-Benz", + "createdAt": "2022-12-16T12:06:28.208036", + "updatedAt": "2023-02-22T00:58:01.733471", + "status": "READY", + "type": "Virtual Vehicle", + "fleets": [ + { + "id": "256", + "name": "fleet bb", + "type": "STATIC" + }, + { + "id": "31", + "name": "411", + "type": "DYNAMIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "devices": [ + { + "id": "18d4285b-31c5-4e35-8b1c-ed931b3baed3", + "type": "TCU", + "status": "ACTIVE", + "createdAt": "2022-12-16T12:06:28.209091", + "gatewayId": "", + "modelType": "HIGH_EU", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-30T16:44:49.170935", + "dmProtocolVersion": "1.0.2", + "components": [ + { + "id": "BB341", + "name": "Component", + "status": "ACTIVE", + "version": "24edc320", + "environmentType": "Software" + } + ] + } + ], + "services": [] + }, + { + "vin": "VINTESTTBB0003014", + "country": "GR", + "model": "A1", + "brand": "Audi", + "createdAt": "2022-12-19T09:41:52.37476", + "updatedAt": "2023-02-22T00:58:01.734666", + "status": "DRAFT", + "type": "Test Bench", + "fleets": [ + { + "id": "368", + "name": "newbb", + "type": "STATIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "devices": [ + { + "id": "TESTDEVICE3014", + "type": "TCU", + "status": "READY", + "createdAt": "2022-12-19T09:41:52.374781", + "gatewayId": "", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-19T09:41:53.877784", + "dmProtocolVersion": "1.0.2", + "components": [ + { + "id": "1", + "name": "Software", + "status": "ACTIVE", + "version": "1681190070", + "environmentType": "Software" + } + ] + } + ], + "services": [] + }, + { + "vin": "VINTESTTBB0003013", + "country": "LU", + "model": "Puma", + "brand": "Ford", + "createdAt": "2022-12-19T09:41:56.025012", + "updatedAt": "2023-02-22T00:58:01.729348", + "status": "DRAFT", + "type": "Test Bench", + "fleets": [ + { + "id": "368", + "name": "newbb", + "type": "STATIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "devices": [ + { + "id": "TESTDEVICE3013", + "type": "TCU", + "status": "READY", + "createdAt": "2022-12-19T09:41:56.025058", + "gatewayId": "", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-19T09:41:56.025058", + "dmProtocolVersion": "1.0.2", + "components": [ + { + "id": "1", + "name": "Software", + "status": "ACTIVE", + "version": "1779121250", + "environmentType": "Software" + } + ] + } + ], + "services": [] + }, + { + "vin": "VINTESTTBB0002940", + "country": "PL", + "model": "Polo", + "brand": "VW", + "createdAt": "2022-12-19T09:42:03.857758", + "updatedAt": "2023-02-22T00:58:01.739486", + "status": "DRAFT", + "type": "Real Vehicle", + "fleets": [ + { + "id": "6197", + "name": "cgdszy", + "type": "STATIC" + }, + { + "id": "4303", + "name": "static", + "type": "STATIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "devices": [ + { + "id": "TESTDEVICE2940", + "type": "IVI", + "status": "DRAFT", + "createdAt": "2022-12-19T09:42:03.857775", + "gatewayId": "", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-19T09:42:03.857775", + "dmProtocolVersion": "1.0.2", + "components": [ + { + "id": "1", + "name": "Software", + "status": "ACTIVE", + "version": "1680479438", + "environmentType": "Software" + } + ] + } + ], + "services": [] + }, + { + "vin": "VINTESTTBB0002939", + "country": "GR", + "model": "Almera", + "brand": "Nissan", + "createdAt": "2022-12-19T09:42:11.52793", + "updatedAt": "2023-02-22T00:58:01.729547", + "status": "DRAFT", + "type": "Real Vehicle", + "fleets": [ + { + "id": "32", + "name": "DM4382-Test-Dynamic-fleet_5", + "type": "DYNAMIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "devices": [], + "services": [] + }, + { + "vin": "TESTVIN0000003337", + "ecomDate": "2022-01-01T00:00:00.000Z", + "country": "PL", + "model": "Insignia", + "brand": "Opel", + "createdAt": "2022-12-19T10:59:57.841533", + "updatedAt": "2023-02-22T00:58:01.73495", + "status": "READY", + "type": "Virtual Vehicle", + "fleets": [ + { + "id": "368", + "name": "newbb", + "type": "STATIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "devices": [ + { + "id": "41e5242c-aa6a-4def-ae1a-961d615b746f", + "type": "TCU", + "status": "READY", + "createdAt": "2022-12-19T10:59:57.841549", + "gatewayId": "", + "modelType": "MID_EU", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-19T10:59:57.841549", + "dmProtocolVersion": "3.0.1", + "components": [ + { + "id": "dabb6d9b", + "name": "ComponentTest", + "status": "ACTIVE", + "version": "3.0.1", + "environmentType": "Software" + } + ] + } + ], + "services": [] + }, + { + "vin": "TESTVIN0000003340", + "ecomDate": "2022-01-01T00:00:00.000Z", + "country": "PL", + "model": "Insignia", + "brand": "Opel", + "region": "EU", + "createdAt": "2022-12-19T11:00:05.613406", + "updatedAt": "2023-02-22T00:58:01.73921", + "status": "DRAFT", + "type": "Real Vehicle", + "fleets": [ + { + "id": "368", + "name": "newbb", + "type": "STATIC" + }, + { + "id": "4053", + "name": "Test", + "type": "STATIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "devices": [ + { + "id": "TESTDEVICE3340", + "type": "TCU", + "status": "DRAFT", + "createdAt": "2022-12-19T11:00:05.613424", + "modelType": "MID_EU", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-19T11:00:12.213828", + "dmProtocolVersion": "3.0.3", + "serialNumber": "TESTDEVICE3340", + "components": [ + { + "id": "1", + "name": "ComponentTest", + "status": "ACTIVE", + "version": "3.0.5", + "environmentType": "Software" + } + ] + } + ], + "services": [] + }, + { + "vin": "TESTVIN0000003408", + "ecomDate": "2021-04-01", + "country": "PL", + "model": "Life", + "brand": "eGO", + "region": "EU", + "createdAt": "2022-12-19T11:00:15.114168", + "updatedAt": "2023-02-22T00:58:01.738612", + "status": "DRAFT", + "type": "Test Bench", + "fleets": [ + { + "id": "368", + "name": "newbb", + "type": "STATIC" + }, + { + "id": "4053", + "name": "Test", + "type": "STATIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "devices": [], + "services": [] + }, + { + "vin": "VINPM128167865193", + "ecomDate": "2022-07-01", + "country": "FR", + "model": "5 Series", + "brand": "BMW", + "createdAt": "2022-12-19T13:17:01.946904", + "updatedAt": "2023-02-23T00:53:00.110147", + "status": "READY", + "type": "Test Bench", + "fleets": [ + { + "id": "31", + "name": "411", + "type": "DYNAMIC" + }, + { + "id": "1867", + "name": "Fleet_DM5172", + "type": "DYNAMIC" + }, + { + "id": "2478", + "name": "Fleet_DM5172", + "type": "DYNAMIC" + }, + { + "id": "3459", + "name": "Fleet_DM5172", + "type": "DYNAMIC" + }, + { + "id": "770", + "name": "Fleet_DM5172", + "type": "DYNAMIC" + }, + { + "id": "1042", + "name": "Fleet_DM5172", + "type": "DYNAMIC" + }, + { + "id": "3829", + "name": "Fleet_DM5172", + "type": "DYNAMIC" + }, + { + "id": "6295", + "name": "Fleet_DM5172", + "type": "DYNAMIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + }, + { + "id": "2082", + "name": "Fleet_DM5172", + "type": "DYNAMIC" + }, + { + "id": "6905", + "name": "Updated name", + "type": "DYNAMIC" + }, + { + "id": "6911", + "name": "Fleet_DM4624", + "type": "DYNAMIC" + } + ], + "devices": [ + { + "id": "DEVICEID665457956", + "type": "TCU", + "status": "READY", + "createdAt": "2022-12-19T13:17:01.946954", + "modelType": "OTA_INTEGRATION_TEST", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-19T13:17:01.946954", + "dmProtocolVersion": "1.01", + "components": [ + { + "id": "1", + "name": "COMPONENT", + "status": "ACTIVE", + "version": "9824902229", + "environmentType": "Software" + }, + { + "id": "2", + "name": "COMPONENT", + "status": "ACTIVE", + "version": "9824902229", + "environmentType": "Software" + } + ] + }, + { + "id": "DEVICEID773051816", + "type": "IVI", + "status": "READY", + "createdAt": "2022-12-19T13:17:01.946972", + "gatewayId": "DEVICEID665457956", + "modelType": "OTA_INTEGRATION_TEST", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-19T13:17:01.946972", + "dmProtocolVersion": "1.01", + "components": [ + { + "id": "3", + "name": "COMPONENT", + "status": "ACTIVE", + "version": "9824902229", + "environmentType": "Software" + }, + { + "id": "4", + "name": "COMPONENT", + "status": "ACTIVE", + "version": "9824902229", + "environmentType": "Software" + } + ] + } + ], + "services": [] + } + ], + "empty": false, + "first": true, + "last": true, + "page": 0, + "size": 10, + "pages": 1, + "elements": 10, + "total": 10 +} diff --git a/tracks-management-service/app/src/test/java/com/tsystems/dco/track/controller/TrackControllerTest.java b/tracks-management-service/app/src/test/java/com/tsystems/dco/track/controller/TrackControllerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c3ff7d2966c9f676988b61912b0eb9931277d35e --- /dev/null +++ b/tracks-management-service/app/src/test/java/com/tsystems/dco/track/controller/TrackControllerTest.java @@ -0,0 +1,162 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.track.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.tsystems.dco.App; +import com.tsystems.dco.model.Track; +import com.tsystems.dco.model.TrackInput; +import com.tsystems.dco.model.TrackPage; +import com.tsystems.dco.track.service.TrackService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; +import java.util.UUID; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.verify; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ContextConfiguration(classes = App.class) +@AutoConfigureMockMvc +class TrackControllerTest { + + @Autowired + private MockMvc mockMvc; + @MockBean + private TrackService trackService; + private static final String TEST = "test"; + + + @Test + void createTrack() throws Exception { + given(trackService.createTrack(any())).willReturn(Track.builder().name(TEST).build()); + TrackInput trackInput = TrackInput.builder().name(TEST).build(); + ObjectMapper objectMapper = new ObjectMapper(); + mockMvc.perform(post("/api/track") + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", getHeader()) + .content(objectMapper.writeValueAsString(trackInput))) + .andDo(print()) + .andExpect(status().isCreated()).andReturn(); + verify(trackService).createTrack(any()); + } + + @Test + void deleteTrackById() throws Exception { + doNothing().when(trackService).deleteTrackById(any()); + mockMvc.perform(delete("/api/track?id=" + UUID.randomUUID()) + .header("Authorization", getHeader()) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isNoContent()); + verify(trackService).deleteTrackById(any()); + } + + @Test + void findTrackById() throws Exception { + given(trackService.findTrackById(any())).willReturn(Track.builder().build()); + mockMvc.perform(get("/api/track/" + UUID.randomUUID()) + .header("Authorization", getHeader()) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()); + verify(trackService).findTrackById(any()); + } + + @Test + void findTrackByIds() throws Exception { + List<Track> trackList = new ArrayList<>(); + trackList.add(Track.builder().build()); + given(trackService.findTrackByIds(any())).willReturn(trackList); + mockMvc.perform(get("/api/track/list?trackIds=" + UUID.randomUUID()) + .header("Authorization", getHeader()) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()); + verify(trackService).findTrackByIds(any()); + } + + @Test + void isTracksExists() throws Exception { + given(trackService.isTracksExists(any())).willReturn(true); + mockMvc.perform(get("/api/track/validate?trackIds=" + UUID.randomUUID()) + .header("Authorization", getHeader()) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()); + verify(trackService).isTracksExists(any()); + } + + @Test + void searchTrackByPattern() throws Exception { + given(trackService.searchTrackByPattern(anyString(), anyInt(), anyInt())).willReturn(new TrackPage()); + mockMvc.perform(get("/api/track/search?trackPattern=abc&page=0&size=10") + .header("Authorization", getHeader()) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()); + verify(trackService).searchTrackByPattern(anyString(), anyInt(), anyInt()); + } + + @Test + void trackReadByQuery() throws Exception { + given(trackService.readTrackByQuery(any(), any(), any(), any(), any())).willReturn(new TrackPage()); + mockMvc.perform(get("/api/track?page=0&size=10") + .header("Authorization", getHeader()) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()); + verify(trackService).readTrackByQuery(any(), any(), any(), any(), any()); + } + + @Test + void getHardwareModule() throws Exception { + given(trackService.getHardwareModule()).willReturn(new ArrayList<>()); + mockMvc.perform(get("/api/track/hardware") + .header("Authorization", getHeader()) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()); + verify(trackService).getHardwareModule(); + } + + private String getHeader(){ + return "Basic "+ Base64.getEncoder().encodeToString("developer:password".getBytes()); + } +} diff --git a/tracks-management-service/app/src/test/java/com/tsystems/dco/track/service/TrackQueryTest.java b/tracks-management-service/app/src/test/java/com/tsystems/dco/track/service/TrackQueryTest.java new file mode 100644 index 0000000000000000000000000000000000000000..0f4bed956aa40a79888d33c14e37d10812c1db06 --- /dev/null +++ b/tracks-management-service/app/src/test/java/com/tsystems/dco/track/service/TrackQueryTest.java @@ -0,0 +1,48 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.track.service; + +import com.tsystems.dco.exception.BaseException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class TrackQueryTest { + + + @Test + void getQueries() { + var raw = "brand:vw,brand!bmw,brand~benz,brand$invalid"; + var query = new TrackQuery(raw); + assertEquals(3, query.getQueries().size()); + var spec = query.toSpecification(); + assertNotNull(spec); + try { + new TrackQuery("brandvw").isValid().toSpecification(); + } catch (Exception e) { + assertInstanceOf(BaseException.class, e); + } + } +} + diff --git a/tracks-management-service/app/src/test/java/com/tsystems/dco/track/service/TrackServiceTest.java b/tracks-management-service/app/src/test/java/com/tsystems/dco/track/service/TrackServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c1998539b8c5a1bff2026c2ce745169bbc82bae6 --- /dev/null +++ b/tracks-management-service/app/src/test/java/com/tsystems/dco/track/service/TrackServiceTest.java @@ -0,0 +1,171 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.track.service; + +import com.tsystems.dco.exception.DataDeletionException; +import com.tsystems.dco.exception.DataNotFoundException; +import com.tsystems.dco.integration.ScenarioApiClient; +import com.tsystems.dco.model.Track; +import com.tsystems.dco.model.TrackInput; +import com.tsystems.dco.model.VehicleResponse; +import com.tsystems.dco.track.entity.TrackEntity; +import com.tsystems.dco.track.repository.TrackRepository; +import com.tsystems.dco.vehicle.entity.VehicleEntity; +import com.tsystems.dco.vehicle.service.VehicleService; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.http.ResponseEntity; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class TrackServiceTest { + + @InjectMocks + private TrackServiceImpl trackService; + @Mock + private PageRequest pageRequest; + @Mock + private TrackRepository trackRepository; + @Mock + private Page<TrackEntity> trackEntityPage; + @Mock + ResponseEntity<Boolean> booleanResponseEntity; + @Mock + private ScenarioApiClient scenarioApiClient; + @Mock + private Specification<TrackEntity> trackEntitySpecification; + @Mock + private VehicleService vehicleService; + private final String TEST = "TEST"; + + @Test + void createTrack() { + TrackInput trackInput = TrackInput.builder().name(TEST).build(); + given(trackRepository.save(any())).willReturn(TrackEntity.builder().name(TEST).build()); + Track scenario = trackService.createTrack(trackInput); + assertEquals(TEST, scenario.getName()); + verify(trackRepository).save(any()); + } + + @Test + void deleteTrackById() { + TrackEntity trackEntity = TrackEntity.builder().name(TEST).build(); + given(trackRepository.findById(any())).willReturn(Optional.ofNullable(trackEntity)); + given(scenarioApiClient.isTrackAssociatedWithSimulation(any())).willReturn(booleanResponseEntity); + given(booleanResponseEntity.getBody()).willReturn(false); + trackService.deleteTrackById(UUID.randomUUID()); + verify(scenarioApiClient).isTrackAssociatedWithSimulation(any()); + } + + @Test + void deleteTrackByIdWithError() { + TrackEntity trackEntity = TrackEntity.builder().name(TEST).build(); + given(trackRepository.findById(any())).willReturn(Optional.ofNullable(trackEntity)); + given(scenarioApiClient.isTrackAssociatedWithSimulation(any())).willReturn(booleanResponseEntity); + given(booleanResponseEntity.getBody()).willReturn(true); + UUID uuid = UUID.randomUUID(); + assertThrows(DataDeletionException.class, () -> trackService.deleteTrackById(uuid)); + } + + @Test + void readTrackByQuery() { + given(trackRepository.findAll(any(Specification.class), any(PageRequest.class))).willReturn(trackEntityPage); + trackService.readTrackByQuery(null, TEST, 0, 10, null); + } + + @Test + void findTrackById() { + VehicleEntity vehicleEntity = VehicleEntity.builder().build(); + List<VehicleEntity> vehicleEntities = new ArrayList<>(); + vehicleEntities.add(vehicleEntity); + TrackEntity trackEntity = TrackEntity.builder().name(TEST).vehicles(vehicleEntities).build(); + given(trackRepository.findById(any())).willReturn(Optional.ofNullable(trackEntity)); + given(vehicleService.getVehicleByVin(any())).willReturn(new VehicleResponse()); + assertEquals(TEST, trackService.findTrackById(UUID.randomUUID()).getName()); + verify(trackRepository).findById(any()); + } + + @Test + void searchTrackByPattern() { + given(trackEntityPage.getPageable()).willReturn(pageRequest); + given(trackEntityPage.getPageable().getPageNumber()).willReturn(1); + given(trackEntityPage.getPageable().getPageSize()).willReturn(1); + given(trackRepository.findByTrackLike(any(), any())).willReturn(trackEntityPage); + assertEquals(1, trackService.searchTrackByPattern(TEST, 0, 10).getSize()); + verify(trackRepository).findByTrackLike(any(), any()); + } + + @Test + void findTrackByIds() { + VehicleEntity vehicleEntity = VehicleEntity.builder().build(); + List<VehicleEntity> vehicleEntities = new ArrayList<>(); + vehicleEntities.add(vehicleEntity); + TrackEntity trackEntity = TrackEntity.builder().name(TEST).vehicles(vehicleEntities).build(); + List<TrackEntity> trackEntities = new ArrayList<>(); + trackEntities.add(trackEntity); + given(trackRepository.findTrackByIds(any())).willReturn(trackEntities); + given(vehicleService.getVehicleByVin(any())).willReturn(new VehicleResponse()); + List<UUID> trackIds = List.of(UUID.randomUUID(), UUID.randomUUID()); + trackService.findTrackByIds(trackIds); + verify(trackRepository).findTrackByIds(any()); + } + + @Test + void isTracksExists() { + List<UUID> trackIds = List.of(UUID.randomUUID(), UUID.randomUUID()); + given(trackRepository.existsById(any())).willReturn(true); + assertTrue(trackService.isTracksExists(trackIds)); + } + + @Test + void isTracksExistsWithError() { + List<UUID> trackIds = List.of(UUID.randomUUID(), UUID.randomUUID()); + given(trackRepository.existsById(any())).willReturn(false); + assertThrows(DataNotFoundException.class, () -> trackService.isTracksExists(trackIds)); + } + + @Test + void getHardwareModule() { + assertNotNull(trackService.getHardwareModule()); + } +} diff --git a/tracks-management-service/app/src/test/java/com/tsystems/dco/vehicle/controller/VehicleControllerTest.java b/tracks-management-service/app/src/test/java/com/tsystems/dco/vehicle/controller/VehicleControllerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..68ed0851cdd90777cd01cd8170e7c8447f136712 --- /dev/null +++ b/tracks-management-service/app/src/test/java/com/tsystems/dco/vehicle/controller/VehicleControllerTest.java @@ -0,0 +1,83 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.vehicle.controller; + +import com.tsystems.dco.App; +import com.tsystems.dco.model.VehiclePage; +import com.tsystems.dco.model.VehicleResponse; +import com.tsystems.dco.vehicle.service.VehicleService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.Base64; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ContextConfiguration(classes = App.class) +@AutoConfigureMockMvc +class VehicleControllerTest { + + @Autowired + private MockMvc mockMvc; + @MockBean + private VehicleService vehicleService; + + @Test + void vehicleReadByQuery() throws Exception { + given(vehicleService.getVehicles(any(), any(), any(), any(), any())).willReturn(new VehiclePage()); + mockMvc.perform(get("/api/vehicle?page=0&size=10") + .header("Authorization", getHeader()) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()); + verify(vehicleService).getVehicles(any(), any(), any(), any(), any()); + } + + @Test + void getVehicleByVin() throws Exception { + given(vehicleService.getVehicleByVin(any())).willReturn(new VehicleResponse()); + mockMvc.perform(get("/api/vehicle/VINTEST") + .header("Authorization", getHeader()) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()); + verify(vehicleService).getVehicleByVin(any()); + } + + private String getHeader(){ + return "Basic "+ Base64.getEncoder().encodeToString("developer:password".getBytes()); + } +} diff --git a/tracks-management-service/app/src/test/java/com/tsystems/dco/vehicle/service/VehicleServiceTest.java b/tracks-management-service/app/src/test/java/com/tsystems/dco/vehicle/service/VehicleServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c1567d8b09dcd54d798d8b1bea632347db996488 --- /dev/null +++ b/tracks-management-service/app/src/test/java/com/tsystems/dco/vehicle/service/VehicleServiceTest.java @@ -0,0 +1,84 @@ +/* + * ======================================================================== + * SDV Developer Console + * + * Copyright (C) 2022 - 2023 T-Systems International GmbH + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * + * ======================================================================== + */ + +package com.tsystems.dco.vehicle.service; + +import com.tsystems.dco.exception.DataNotFoundException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; + +@ExtendWith(MockitoExtension.class) +class VehicleServiceTest { + + @InjectMocks + private VehicleServiceImpl vehicleService; + @Mock + private ResourceLoader resourceLoader; + @Mock + private Resource resource; + + @Test + void getVehicles() throws IOException { + getVehicleData(); + assertNotNull(vehicleService.getVehicles(null, null, 0, 10, null)); + } + + @Test + void getVehiclesWithError() throws IOException { + given(resourceLoader.getResource(any())).willReturn(resource); + given(resource.getInputStream()).willThrow(IOException.class); + assertThrows(DataNotFoundException.class, () -> vehicleService.getVehicles(null, null, 0, 10, null)); + } + + @Test + void getVehicleByVin() throws IOException { + getVehicleData(); + assertNotNull(vehicleService.getVehicleByVin("BBTEST00000000340")); + } + + @Test + void getVehicleByVinWithError() throws IOException { + getVehicleData(); + assertThrows(DataNotFoundException.class, () -> vehicleService.getVehicleByVin("test")); + } + + private void getVehicleData() throws IOException { + InputStream inputStream = new FileInputStream("src/test/resources/vehicle-test.json"); + given(resourceLoader.getResource(any())).willReturn(resource); + given(resource.getInputStream()).willReturn(inputStream); + } +} diff --git a/tracks-management-service/app/src/test/resources/vehicle-test.json b/tracks-management-service/app/src/test/resources/vehicle-test.json new file mode 100644 index 0000000000000000000000000000000000000000..3332b46c85f80695270b33a4b191cd4aa4829d04 --- /dev/null +++ b/tracks-management-service/app/src/test/resources/vehicle-test.json @@ -0,0 +1,545 @@ +{ + "content": [ + { + "vin": "BBTEST00000000340", + "ecomDate": "2022-12-15T21:30:32.105Z", + "country": "FR", + "model": "A-Class", + "brand": "Mercedes-Benz", + "createdAt": "2022-12-15T21:30:46.443421", + "updatedAt": "2023-02-22T00:58:01.734361", + "status": "DRAFT", + "type": "Virtual Vehicle", + "fleets": [ + { + "id": "256", + "name": "fleet bb", + "type": "STATIC" + }, + { + "id": "31", + "name": "411", + "type": "DYNAMIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "devices": [ + { + "id": "84c32703-982a-4efd-920b-b80977073b33", + "type": "TCU", + "status": "ACTIVE", + "createdAt": "2022-12-15T21:30:46.443445", + "gatewayId": "", + "modelType": "HIGH_EU", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-20T14:09:25.437637", + "dmProtocolVersion": "1.0.2", + "components": [ + { + "id": "747243cc", + "name": "Component", + "status": "ACTIVE", + "version": "24edc320", + "environmentType": "Software" + } + ] + }, + { + "id": "36e5322b-92ce-4fb9-ac25-56d2ed0c4b53", + "type": "IVI", + "status": "DRAFT", + "createdAt": "2022-12-20T14:09:12.829388", + "gatewayId": "", + "modelType": "HIGH_EU", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-20T14:09:25.48871", + "dmProtocolVersion": "1.0.2", + "components": [] + } + ], + "services": [] + }, + { + "vin": "BBTEST00000000341", + "ecomDate": "2022-12-16T12:06:09.865Z", + "country": "FR", + "model": "A-Class", + "brand": "Mercedes-Benz", + "createdAt": "2022-12-16T12:06:28.208036", + "updatedAt": "2023-02-22T00:58:01.733471", + "status": "READY", + "type": "Virtual Vehicle", + "fleets": [ + { + "id": "256", + "name": "fleet bb", + "type": "STATIC" + }, + { + "id": "31", + "name": "411", + "type": "DYNAMIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "devices": [ + { + "id": "18d4285b-31c5-4e35-8b1c-ed931b3baed3", + "type": "TCU", + "status": "ACTIVE", + "createdAt": "2022-12-16T12:06:28.209091", + "gatewayId": "", + "modelType": "HIGH_EU", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-30T16:44:49.170935", + "dmProtocolVersion": "1.0.2", + "components": [ + { + "id": "BB341", + "name": "Component", + "status": "ACTIVE", + "version": "24edc320", + "environmentType": "Software" + } + ] + } + ], + "services": [] + }, + { + "vin": "VINTESTTBB0003014", + "country": "GR", + "model": "A1", + "brand": "Audi", + "createdAt": "2022-12-19T09:41:52.37476", + "updatedAt": "2023-02-22T00:58:01.734666", + "status": "DRAFT", + "type": "Test Bench", + "fleets": [ + { + "id": "368", + "name": "newbb", + "type": "STATIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "devices": [ + { + "id": "TESTDEVICE3014", + "type": "TCU", + "status": "READY", + "createdAt": "2022-12-19T09:41:52.374781", + "gatewayId": "", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-19T09:41:53.877784", + "dmProtocolVersion": "1.0.2", + "components": [ + { + "id": "1", + "name": "Software", + "status": "ACTIVE", + "version": "1681190070", + "environmentType": "Software" + } + ] + } + ], + "services": [] + }, + { + "vin": "VINTESTTBB0003013", + "country": "LU", + "model": "Puma", + "brand": "Ford", + "createdAt": "2022-12-19T09:41:56.025012", + "updatedAt": "2023-02-22T00:58:01.729348", + "status": "DRAFT", + "type": "Test Bench", + "fleets": [ + { + "id": "368", + "name": "newbb", + "type": "STATIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "devices": [ + { + "id": "TESTDEVICE3013", + "type": "TCU", + "status": "READY", + "createdAt": "2022-12-19T09:41:56.025058", + "gatewayId": "", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-19T09:41:56.025058", + "dmProtocolVersion": "1.0.2", + "components": [ + { + "id": "1", + "name": "Software", + "status": "ACTIVE", + "version": "1779121250", + "environmentType": "Software" + } + ] + } + ], + "services": [] + }, + { + "vin": "VINTESTTBB0002940", + "country": "PL", + "model": "Polo", + "brand": "VW", + "createdAt": "2022-12-19T09:42:03.857758", + "updatedAt": "2023-02-22T00:58:01.739486", + "status": "DRAFT", + "type": "Real Vehicle", + "fleets": [ + { + "id": "6197", + "name": "cgdszy", + "type": "STATIC" + }, + { + "id": "4303", + "name": "static", + "type": "STATIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "devices": [ + { + "id": "TESTDEVICE2940", + "type": "IVI", + "status": "DRAFT", + "createdAt": "2022-12-19T09:42:03.857775", + "gatewayId": "", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-19T09:42:03.857775", + "dmProtocolVersion": "1.0.2", + "components": [ + { + "id": "1", + "name": "Software", + "status": "ACTIVE", + "version": "1680479438", + "environmentType": "Software" + } + ] + } + ], + "services": [] + }, + { + "vin": "VINTESTTBB0002939", + "country": "GR", + "model": "Almera", + "brand": "Nissan", + "createdAt": "2022-12-19T09:42:11.52793", + "updatedAt": "2023-02-22T00:58:01.729547", + "status": "DRAFT", + "type": "Real Vehicle", + "fleets": [ + { + "id": "32", + "name": "DM4382-Test-Dynamic-fleet_5", + "type": "DYNAMIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "devices": [], + "services": [] + }, + { + "vin": "TESTVIN0000003337", + "ecomDate": "2022-01-01T00:00:00.000Z", + "country": "PL", + "model": "Insignia", + "brand": "Opel", + "createdAt": "2022-12-19T10:59:57.841533", + "updatedAt": "2023-02-22T00:58:01.73495", + "status": "READY", + "type": "Virtual Vehicle", + "fleets": [ + { + "id": "368", + "name": "newbb", + "type": "STATIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "devices": [ + { + "id": "41e5242c-aa6a-4def-ae1a-961d615b746f", + "type": "TCU", + "status": "READY", + "createdAt": "2022-12-19T10:59:57.841549", + "gatewayId": "", + "modelType": "MID_EU", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-19T10:59:57.841549", + "dmProtocolVersion": "3.0.1", + "components": [ + { + "id": "dabb6d9b", + "name": "ComponentTest", + "status": "ACTIVE", + "version": "3.0.1", + "environmentType": "Software" + } + ] + } + ], + "services": [] + }, + { + "vin": "TESTVIN0000003340", + "ecomDate": "2022-01-01T00:00:00.000Z", + "country": "PL", + "model": "Insignia", + "brand": "Opel", + "region": "EU", + "createdAt": "2022-12-19T11:00:05.613406", + "updatedAt": "2023-02-22T00:58:01.73921", + "status": "DRAFT", + "type": "Real Vehicle", + "fleets": [ + { + "id": "368", + "name": "newbb", + "type": "STATIC" + }, + { + "id": "4053", + "name": "Test", + "type": "STATIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "devices": [ + { + "id": "TESTDEVICE3340", + "type": "TCU", + "status": "DRAFT", + "createdAt": "2022-12-19T11:00:05.613424", + "modelType": "MID_EU", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-19T11:00:12.213828", + "dmProtocolVersion": "3.0.3", + "serialNumber": "TESTDEVICE3340", + "components": [ + { + "id": "1", + "name": "ComponentTest", + "status": "ACTIVE", + "version": "3.0.5", + "environmentType": "Software" + } + ] + } + ], + "services": [] + }, + { + "vin": "TESTVIN0000003408", + "ecomDate": "2021-04-01", + "country": "PL", + "model": "Life", + "brand": "eGO", + "region": "EU", + "createdAt": "2022-12-19T11:00:15.114168", + "updatedAt": "2023-02-22T00:58:01.738612", + "status": "DRAFT", + "type": "Test Bench", + "fleets": [ + { + "id": "368", + "name": "newbb", + "type": "STATIC" + }, + { + "id": "4053", + "name": "Test", + "type": "STATIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + } + ], + "devices": [], + "services": [] + }, + { + "vin": "VINPM128167865193", + "ecomDate": "2022-07-01", + "country": "FR", + "model": "5 Series", + "brand": "BMW", + "createdAt": "2022-12-19T13:17:01.946904", + "updatedAt": "2023-02-23T00:53:00.110147", + "status": "READY", + "type": "Test Bench", + "fleets": [ + { + "id": "31", + "name": "411", + "type": "DYNAMIC" + }, + { + "id": "1867", + "name": "Fleet_DM5172", + "type": "DYNAMIC" + }, + { + "id": "2478", + "name": "Fleet_DM5172", + "type": "DYNAMIC" + }, + { + "id": "3459", + "name": "Fleet_DM5172", + "type": "DYNAMIC" + }, + { + "id": "770", + "name": "Fleet_DM5172", + "type": "DYNAMIC" + }, + { + "id": "1042", + "name": "Fleet_DM5172", + "type": "DYNAMIC" + }, + { + "id": "3829", + "name": "Fleet_DM5172", + "type": "DYNAMIC" + }, + { + "id": "6295", + "name": "Fleet_DM5172", + "type": "DYNAMIC" + }, + { + "id": "6724", + "name": "Test", + "type": "STATIC" + }, + { + "id": "2082", + "name": "Fleet_DM5172", + "type": "DYNAMIC" + }, + { + "id": "6905", + "name": "Updated name", + "type": "DYNAMIC" + }, + { + "id": "6911", + "name": "Fleet_DM4624", + "type": "DYNAMIC" + } + ], + "devices": [ + { + "id": "DEVICEID665457956", + "type": "TCU", + "status": "READY", + "createdAt": "2022-12-19T13:17:01.946954", + "modelType": "OTA_INTEGRATION_TEST", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-19T13:17:01.946954", + "dmProtocolVersion": "1.01", + "components": [ + { + "id": "1", + "name": "COMPONENT", + "status": "ACTIVE", + "version": "9824902229", + "environmentType": "Software" + }, + { + "id": "2", + "name": "COMPONENT", + "status": "ACTIVE", + "version": "9824902229", + "environmentType": "Software" + } + ] + }, + { + "id": "DEVICEID773051816", + "type": "IVI", + "status": "READY", + "createdAt": "2022-12-19T13:17:01.946972", + "gatewayId": "DEVICEID665457956", + "modelType": "OTA_INTEGRATION_TEST", + "dmProtocol": "LWM2M", + "modifiedAt": "2022-12-19T13:17:01.946972", + "dmProtocolVersion": "1.01", + "components": [ + { + "id": "3", + "name": "COMPONENT", + "status": "ACTIVE", + "version": "9824902229", + "environmentType": "Software" + }, + { + "id": "4", + "name": "COMPONENT", + "status": "ACTIVE", + "version": "9824902229", + "environmentType": "Software" + } + ] + } + ], + "services": [] + } + ], + "empty": false, + "first": true, + "last": true, + "page": 0, + "size": 10, + "pages": 1, + "elements": 10, + "total": 10 +} diff --git a/tracks-management-service/pom.xml b/tracks-management-service/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..560f948b4ae886315034f036582764218023f64a --- /dev/null +++ b/tracks-management-service/pom.xml @@ -0,0 +1,217 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <groupId>com.tsystems.dco</groupId> + <artifactId>tracks-management-service</artifactId> + <packaging>pom</packaging> + <version>latest</version> + <modules> + <module>api</module> + <module>app</module> + <module>app-database</module> + </modules> + + <organization> + <name>T-Systems International GmbH</name> + <url>https://t-systems.com</url> + </organization> + + <developers> + <developer> + <name>T-Systems</name> + <email>info@t-systems.com</email> + <organization>T-Systems International GmbH</organization> + <organizationUrl>https://t-systems.com</organizationUrl> + </developer> + </developers> + + <properties> + <!-- java --> + <java.version>17</java.version> + <maven.compiler.source>17</maven.compiler.source> + <maven.compiler.target>17</maven.compiler.target> + + <spring.cloud.version>2022.0.3</spring.cloud.version> + <spring.boot.version>3.1.0</spring.boot.version> + <!-- charset --> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> + <aws.sdk.version>2.17.295</aws.sdk.version> + </properties> + + <dependencyManagement> + <dependencies> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-dependencies</artifactId> + <version>${spring.boot.version}</version> + <type>pom</type> + <scope>import</scope> + </dependency> + <dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-dependencies</artifactId> + <version>${spring.cloud.version}</version> + <type>pom</type> + <scope>import</scope> + </dependency> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + <version>1.18.24</version> + </dependency> + <dependency> + <groupId>org.mapstruct</groupId> + <artifactId>mapstruct</artifactId> + <version>1.4.2.Final</version> + </dependency> + <dependency> + <groupId>org.springdoc</groupId> + <artifactId>springdoc-openapi-ui</artifactId> + <version>1.7.0</version> + </dependency> + <dependency> + <groupId>software.amazon.awssdk</groupId> + <artifactId>bom</artifactId> + <version>${aws.sdk.version}</version> + <type>pom</type> + <scope>import</scope> + </dependency> + </dependencies> + </dependencyManagement> + + <build> + <pluginManagement> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-resources-plugin</artifactId> + <version>3.2.0</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.8.1</version> + </plugin> + <plugin> + <groupId>org.openapitools</groupId> + <artifactId>openapi-generator-maven-plugin</artifactId> + <version>6.6.0</version> + </plugin> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + <version>${spring.boot.version}</version> + </plugin> + <plugin> + <groupId>io.github.pmckeown</groupId> + <artifactId>dependency-track-maven-plugin</artifactId> + <version>1.1.3</version> + </plugin> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + <version>0.8.8</version> + </plugin> + <plugin> + <groupId>org.sonarsource.scanner.maven</groupId> + <artifactId>sonar-maven-plugin</artifactId> + <version>3.9.1.2184</version> + </plugin> + </plugins> + </pluginManagement> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <version>3.0.0-M5</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-resources-plugin</artifactId> + <configuration> + <delimiters> + <delimiter>@</delimiter> + </delimiters> + <useDefaultDelimiters>false</useDefaultDelimiters> + </configuration> + </plugin> + <plugin> + <groupId>com.github.spotbugs</groupId> + <artifactId>spotbugs-maven-plugin</artifactId> + <configuration> + <failOnError>false</failOnError> + <outputEncoding>UTF-8</outputEncoding> + <xmlOutput>true</xmlOutput> + </configuration> + <executions> + <execution> + <id>check</id> + <phase>package</phase> + <goals> + <goal>check</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + <executions> + <execution> + <id>prepare-agent</id> + <goals> + <goal>prepare-agent</goal> + </goals> + </execution> + <execution> + <id>report</id> + <phase>test</phase> + <goals> + <goal>report</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.cyclonedx</groupId> + <artifactId>cyclonedx-maven-plugin</artifactId> + <executions> + <execution> + <id>aggregate</id> + <phase>package</phase> + <goals> + <goal>makeAggregateBom</goal> + </goals> + </execution> + </executions> + <configuration> + <projectType>application</projectType> + <schemaVersion>1.4</schemaVersion> + <includeBomSerialNumber>true</includeBomSerialNumber> + <includeCompileScope>true</includeCompileScope> + <includeRuntimeScope>true</includeRuntimeScope> + <includeProvidedScope>false</includeProvidedScope> + <includeSystemScope>true</includeSystemScope> + <includeTestScope>false</includeTestScope> + <includeLicenseText>false</includeLicenseText> + <outputReactorProjects>true</outputReactorProjects> + <outputFormat>all</outputFormat> + <outputName>bom</outputName> + </configuration> + </plugin> + <plugin> + <groupId>io.github.pmckeown</groupId> + <artifactId>dependency-track-maven-plugin</artifactId> + <version>1.1.3</version> + <configuration> + <projectName>${app.name}</projectName> + <projectVersion>latest</projectVersion> + </configuration> + </plugin> + </plugins> + </build> +</project> diff --git a/tracks-management-service/settings.xml b/tracks-management-service/settings.xml new file mode 100644 index 0000000000000000000000000000000000000000..0f70d3d49a0624721d6ee009d20d64187604bd25 --- /dev/null +++ b/tracks-management-service/settings.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<settings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/SETTINGS/1.0.0" + xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd"> + <localRepository>${HOME}/.m2/repository</localRepository> + <interactiveMode>false</interactiveMode> + <offline/> + <pluginGroups/> + <servers/> + <mirrors/> + <proxies/> + <profiles/> + <activeProfiles/> +</settings>