Merge remote-tracking branch 'origin/main' into feature/process_model_unit_tests

This commit is contained in:
jasquat 2023-05-22 15:17:49 -04:00
commit 3f6bc76a7e
63 changed files with 2938 additions and 3452 deletions

View File

@ -75,7 +75,7 @@ jobs:
database: "sqlite", database: "sqlite",
} }
- { python: "3.11", os: "ubuntu-latest", session: "xdoctest" } - { python: "3.11", os: "ubuntu-latest", session: "xdoctest" }
- { python: "3.11", os: "ubuntu-latest", session: "docs-build" } # - { python: "3.11", os: "ubuntu-latest", session: "docs-build" }
env: env:
FLASK_SESSION_SECRET_KEY: super_secret_key FLASK_SESSION_SECRET_KEY: super_secret_key
@ -119,20 +119,23 @@ jobs:
pipx inject --pip-args=--constraint=.github/workflows/constraints.txt nox nox-poetry pipx inject --pip-args=--constraint=.github/workflows/constraints.txt nox nox-poetry
nox --version nox --version
- name: Checkout Samples # when we get an imcompatible sqlite migration again and need to combine all migrations into one for the benefit of sqlite
if: matrix.database == 'sqlite' # see if we can get the sqlite-specific block in the noxfile.py to work instead of this block in the github workflow,
uses: actions/checkout@v3 # which annoyingly runs python setup outside of the nox environment (which seems to be flakier on poetry install).
with: # - name: Checkout Samples
repository: sartography/sample-process-models # if: matrix.database == 'sqlite'
path: sample-process-models # uses: actions/checkout@v3
- name: Poetry Install # with:
if: matrix.database == 'sqlite' # repository: sartography/sample-process-models
run: poetry install # path: sample-process-models
- name: Setup sqlite # - name: Poetry Install
if: matrix.database == 'sqlite' # if: matrix.database == 'sqlite'
env: # run: poetry install
SPIFFWORKFLOW_BACKEND_BPMN_SPEC_ABSOLUTE_DIR: "${GITHUB_WORKSPACE}/sample-process-models" # - name: Setup sqlite
run: ./bin/recreate_db clean rmall # if: matrix.database == 'sqlite'
# env:
# SPIFFWORKFLOW_BACKEND_BPMN_SPEC_ABSOLUTE_DIR: "${GITHUB_WORKSPACE}/sample-process-models"
# run: ./bin/recreate_db clean rmall
- name: Setup Mysql - name: Setup Mysql
uses: mirromutth/mysql-action@v1.1 uses: mirromutth/mysql-action@v1.1
@ -162,13 +165,13 @@ jobs:
name: coverage-data name: coverage-data
path: "spiffworkflow-backend/.coverage.*" path: "spiffworkflow-backend/.coverage.*"
- name: Upload documentation # - name: Upload documentation
if: matrix.session == 'docs-build' # if: matrix.session == 'docs-build'
uses: actions/upload-artifact@v3 # uses: actions/upload-artifact@v3
with: # with:
name: docs # name: docs
path: docs/_build # path: docs/_build
#
- name: Upload logs - name: Upload logs
if: failure() && matrix.session == 'tests' if: failure() && matrix.session == 'tests'
uses: "actions/upload-artifact@v3" uses: "actions/upload-artifact@v3"

View File

@ -91,6 +91,11 @@ jobs:
- name: wait_for_keycloak - name: wait_for_keycloak
working-directory: ./spiffworkflow-backend working-directory: ./spiffworkflow-backend
run: ./keycloak/bin/wait_for_keycloak 5 run: ./keycloak/bin/wait_for_keycloak 5
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: |
echo "$GITHUB_CONTEXT"
- name: Cypress run - name: Cypress run
uses: cypress-io/github-action@v5 uses: cypress-io/github-action@v5
with: with:
@ -99,7 +104,11 @@ jobs:
# only record on push, not pull_request, since we do not have secrets for PRs, # only record on push, not pull_request, since we do not have secrets for PRs,
# so the required CYPRESS_RECORD_KEY will not be available. # so the required CYPRESS_RECORD_KEY will not be available.
# we have limited runs in cypress cloud, so only record main builds # we have limited runs in cypress cloud, so only record main builds
record: ${{ github.ref_name == 'main' && github.event_name == 'push' }} # the direct check for github.event_name == 'push' is for if we want to go back to triggering this workflow
# directly, rather than when Backend Tests complete.
# note that github.event.workflow_run is referring to the Backend Tests workflow and another option
# for github.event.workflow_run.event is 'pull_request', which we want to ignore.
record: ${{ github.ref_name == 'main' && ((github.event_name == 'workflow_run' && github.event.workflow_run.event == 'push') || (github.event_name == 'push')) }}
env: env:
# pass the Dashboard record key as an environment variable # pass the Dashboard record key as an environment variable
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}

View File

@ -15,14 +15,23 @@ If you need to push back from the monorepo to one of the individual repos, here'
git subtree push --prefix=spiffworkflow-frontend git@github.com:sartography/spiffworkflow-frontend.git add_md_file git subtree push --prefix=spiffworkflow-frontend git@github.com:sartography/spiffworkflow-frontend.git add_md_file
Setup ## Backend Setup
-----
First install python and poetry, and then:
cd spiffworkflow-backend
poetry install poetry install
./bin/run_server_locally
Run tests ## Frontend Setup
---------
First install nodejs, ideally the version in .tool-versions (but likely other versions will work). Then:
cd spiffworkflow-frontend
npm install
npm start
## Run tests
./bin/run_pyl ./bin/run_pyl
Requires at root: Requires at root:
@ -31,26 +40,19 @@ Requires at root:
- .pre-commit-config.yaml - .pre-commit-config.yaml
- pyproject.toml - pyproject.toml
Run cypress automated browser tests ## Run cypress automated browser tests
-----------------------------------
Get the app running so you can access the frontend at http://localhost:7001 in your browser. Get the app running so you can access the frontend at http://localhost:7001 in your browser by following the frontend and backend setup steps above, and then:
First install nodejs, ideally the version in .tool-versions (but likely other versions will work).
Then:
cd spiffworkflow-frontend
npm install
./bin/run_cypress_tests_locally ./bin/run_cypress_tests_locally
License ## License
-------
SpiffArena's main components are published under the terms of the SpiffArena's main components are published under the terms of the
[GNU Lesser General Public License (LGPL) Version 3](https://www.gnu.org/licenses/lgpl-3.0.txt). [GNU Lesser General Public License (LGPL) Version 3](https://www.gnu.org/licenses/lgpl-3.0.txt).
Support ## Support
-------
You can find us on [our Discord Channel](https://discord.gg/BYHcc7PpUC). You can find us on [our Discord Channel](https://discord.gg/BYHcc7PpUC).
Commercial support for SpiffWorkflow is available from [Sartography](https://sartography.com). Commercial support for SpiffWorkflow is available from [Sartography](https://sartography.com).

5
docs/.markdownlint.jsonc Normal file
View File

@ -0,0 +1,5 @@
{
"default": true,
"MD013": false,
"whitespace": false
}

View File

@ -10,7 +10,10 @@ BUILDDIR = _build
# Put it first so that "make" without argument is like "make help". # Put it first so that "make" without argument is like "make help".
help: help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) && echo " \033[0;34mlint\033[0m runs markdownlint on all markdown files (this was added to the Makefile manually. pardon formatting)"
lint:
markdownlint **/*.md
.PHONY: help Makefile .PHONY: help Makefile

View File

@ -2,56 +2,61 @@
This documentation is currently hosted live at [Spiff-Arena's ReadTheDocs](https://spiff-arena.readthedocs.io/en/latest/) This documentation is currently hosted live at [Spiff-Arena's ReadTheDocs](https://spiff-arena.readthedocs.io/en/latest/)
Please set aside a couple of hours to work through this, as getting this setup correctly once is 10,000 times better than having problems every day for the rest of your life. Please set aside a couple of hours to work through this, as getting this setup correctly once is 10,000 times better than having problems every day for the rest of your life.
## Our Methodology ## Our Methodology
The methodology we are following is knowns as ["Docs as Code"](https://www.writethedocs.org/guide/docs-as-code/) The methodology we are following is known as ["Docs as Code"](https://www.writethedocs.org/guide/docs-as-code/).
This means using the same tools and processes that software developers use for writing code to write the documenation for code. This means using the same tools and processes that software developers use for writing code to write the documentation for code.
In following this methodoloy, you will have to pick up some tools you haven't had to use before (Git, Sphinx). In following this methodology, you will have to pick up some tools you haven't had to use before (Git, Sphinx).
Why would a technical writer need to learn these software engineering tools? Why would a technical writer need to learn these software engineering tools?
I'll never make the case as well as an article by [Tom Johnson](https://idratherbewriting.com/trends/trends-to-follow-or-forget-docs-as-code.html). I'll never make the case as well as an article by [Tom Johnson](https://idratherbewriting.com/trends/trends-to-follow-or-forget-docs-as-code.html).
You might notice, when looking at the markdown files, that every sentence starts on a new line. You might notice, when looking at the markdown files, that every sentence starts on a new line.
Like this one. Like this one.
Unless there is a blank line between sentences, Markdown will still render this as a paragraph. Unless there is a blank line between sentences, Markdown will still render this as a paragraph.
This is called [Ventilated Code](https://vanemden.wordpress.com/2009/01/01/ventilated-prose/) and can be very helpful when working in Markdown. This is called [Ventilated Prose](https://vanemden.wordpress.com/2009/01/01/ventilated-prose/) and can be very helpful when working in Markdown.
## Our Tools ## Our Tools
[Markdown](https://www.markdownguide.org/getting-started/) is a "markup language that you can use to add formatting elements to plain text documents. [Markdown](https://www.markdownguide.org/getting-started/) is a "markup language that you can use to add formatting elements to plain text documents."
You won't be writing the documentation in a word processor, but in simple plain text, and using some special syntax that will consistently and professionally format that text. You won't be writing the documentation in a word processor, but in simple plain text, and some special syntax that will consistently and professionally format that text.
The basic Markdown syntax is very simple.
The basic Markdown syntax is very simple. Here are some [quick examples](https://commonmark.org/help/). And here is a great [10 minute tutorial](https://commonmark.org/help/tutorial/). Here are some [quick examples](https://commonmark.org/help/). And here is a great [10 minute tutorial](https://commonmark.org/help/tutorial/).
This will cover a lot of the basics, like bolding text, italics, paragraphs, lists and other common formatting techniques. This will cover a lot of the basics, like bolding text, italics, paragraphs, lists and other common formatting techniques.
![Markdown screenshot](./images/markdown.png "Markdown example") ![Markdown screenshot](./images/markdown.png "Markdown example")
### MyST ### MyST
Markdown doesn't support some really useful things. Markdown doesn't support some really useful things.
You can't add footnotes, or create an "aside" comment or build a table. You can't add footnotes, or create an "aside" comment or build a table.
Because of this there are many extensions typically referened to as Markdown "Flavors". Because of this, there are many extensions, and these are typically referred to as Markdown "Flavors."
The flavor we are using is MyST. The flavor we are using is MyST.
There is [excellent documentation on MyST](https://myst-parser.readthedocs.io/en/v0.13.5/using/syntax.html) that you should definitely review, so you know everthing that is available to you. There is [excellent documentation on MyST](https://myst-parser.readthedocs.io/en/v0.13.5/using/syntax.html) that you should definitely review, so you know everything that is available to you.
### Sphinx ### Sphinx
This is a large documenation effort. Many different Markdown pages will together make up the full website.
This is a large documentation effort.
Many different Markdown pages will together make up the full website.
You will mostly use Sphinx in the background - you won't be aware of it. You will mostly use Sphinx in the background - you won't be aware of it.
But if you decide that you want to alter the theme (the colors, styles, etc...) of the final website, Sphinx controls this and offers [themes](https://sphinx-themes.org/) and the ability to change styles / colors and formatting through the site. But if you decide that you want to alter the theme (the colors, styles, etc...) of the final website, Sphinx controls this and offers [themes](https://sphinx-themes.org/) and the ability to change styles / colors and formatting through the site.
You just need to learn a little CSS to control it. You just need to learn a little CSS to control it.
### GitHub ### GitHub
Our project is managed by a version control system called Git. Our project is managed by a version control system called Git.
You can use GIT to submit changes to the documenation, in the same we use to submit changes to our code. You can use GIT to submit changes to the documentation, in the same we use to submit changes to our code.
It is avilable on GitHub as the [spiff-arena project](https://github.com/sartography/spiff-arena). Git also manages versions of the code, and handles running tests, and causing our documenation to be built and deployed. It is available on GitHub as the [spiff-arena project](https://github.com/sartography/spiff-arena).
It will take a bit to get comfortable with Git, but when you do, you will come to love it (or maybe hate it, but with a lot of respect) GitHub also manages versions of the code and handles running tests.
Readthedocs observes changes in git and manages an automated process that causes our documentation to be built and deployed.
It will take a bit to get comfortable with Git, but when you do, you will come to love it (or maybe hate it, but with a lot of respect).
## Setup ## Setup
@ -60,72 +65,96 @@ But you will find that most of it just works - and that once you get into a regu
### Step 1: Pre-Requisites ### Step 1: Pre-Requisites
Assure you have been granted write access to our repository.
Make sure you have an account on GitHub and then contact dan@sartography.com and ask him to add you as a contributor. Assure you have been granted write access to our git repository.
Make sure you have an account on GitHub and then contact `dan@sartography.com` and ask him to add you as a contributor.
### Step 2: Install VSCode ### Step 2: Install VSCode
[Download VSCode](https://code.visualstudio.com/) and install it on your computer. [Download VSCode](https://code.visualstudio.com/) and install it on your computer.
### Step 3: Install Python ### Step 3: Install Python
We need python in order to build the website locally so we can really see what our content is going to look like once we publish. It's going to be handy for other reasons as well. We'll want python to be properly set up inside of VS Code. Follow [these directions and brief tutorial](https://code.visualstudio.com/docs/python/python-tutorial
) to assure this is set up.
We need python in order to build the website locally so we can really see what our content is going to look like once we publish.
It's going to be handy for other reasons as well.
We'll want python to be properly set up inside of VS Code.
Follow [these directions and brief tutorial](https://code.visualstudio.com/docs/python/python-tutorial) to assure this is set up.
### Step 3: Connect VSCode to Git ### Step 3: Connect VSCode to Git
VSCode comes with Git built in.
So you can use VSCode to "pull" changes from others down to your local computer and "push" changes back up to share with others (and to cause our docs site to rebuild)
Here are directions for how to [clone Spiff-Arena](https://learn.microsoft.com/en-us/azure/developer/javascript/how-to/with-visual-studio-code/clone-github-repository?tabs=create-repo-command-palette%2Cinitialize-repo-activity-bar%2Ccreate-branch-command-palette%2Ccommit-changes-command-palette%2Cpush-command-palette#clone-repository). **IMPORTANT**: Follow those directions, but be sure to checkout https://github.com/sartography/spiff-arena instead of the project they are using! VSCode comes with Git built in.
So you can use VSCode to "pull" changes from others down to your local computer and "push" changes back up to share with others (and to cause our docs site to rebuild).
Here are directions for how to [clone Spiff-Arena](https://learn.microsoft.com/en-us/azure/developer/javascript/how-to/with-visual-studio-code/clone-github-repository?tabs=create-repo-command-palette%2Cinitialize-repo-activity-bar%2Ccreate-branch-command-palette%2Ccommit-changes-command-palette%2Cpush-command-palette#clone-repository).
**IMPORTANT**: Follow those directions, but be sure to checkout `https://github.com/sartography/spiff-arena` instead of the project they are using!
You can save the project to any directory on your computer. You can save the project to any directory on your computer.
We strongly suggest you create a sub-folder called "projects" in your "home" or "Desktop" folder and checkout the code into this directory. We strongly suggest you create a sub-folder called "projects" in your "home" or "Desktop" folder and checkout the code into this directory.
### Step 4: Open just the Docs Folder ### Step 4: Open just the Docs Folder
We've checked out the whole spiff-arena project, but we are only going to be working inside of the docs directory. So let's open just that folder in VSCode. We've checked out the whole spiff-arena project, but we are only going to be working inside of the docs directory.
So let's open just that folder in VSCode.
* Go to File -> Open Folder * Go to File -> Open Folder
* Select the "docs" folder inside of spiff-arena. * Select the "docs" folder inside of spiff-arena.
Now clikc on the two pieces of paper at the top corner of your screen, and you should see a project that looks like this: Now click on the two pieces of paper at the top corner of your screen, and you should see a project that looks like this without all the rest of the code in your way:
![Docs Directory](./images/docs_dir.png "Docs Directory") ![Docs Directory](./images/docs_dir.png "Docs Directory")
Without all the rest of the code in your way.
### Step 4: Add some extensions ### Step 4: Add some extensions
* Inside VSCode, go to File -> Preferences -> Extensions
* Search for "myst" * Inside VSCode, go to File -> Preferences -> Extensions
* click the "install" button. * Search for "myst"
* Repeat, this time doing it for "python extension for VS Code" * click the "install" button.
* Repeat, this time installing the "Python" extension for VS Code (from Microsoft)
![Myst Extension](./images/myst.png "Search or MyST in extensions") ![Myst Extension](./images/myst.png "Search or MyST in extensions")
### Step 5: Install Python Dependencies ### Step 5: Install Python Dependencies
This project requires a few Python dependencies to work correctly. We are going to set up a Virtual Evironment for Python to keep us sane later on. You can do that by following these steps:
This project requires a few Python dependencies to work correctly.
We are going to set up a Virtual Environment for Python to keep us sane later on.
You can do that by following these steps:
1. Open the Command Palette (Ctrl+Shift+P), start typing the **Python: Create Environment** command to search, and then select the command. 1. Open the Command Palette (Ctrl+Shift+P), start typing the **Python: Create Environment** command to search, and then select the command.
1. Select **Venv** 1. Select **Venv**
1. Select Python 3.11 from the list of options if there is nore than one thing to select. 1. Select Python 3.11 from the list of options if there is more than one thing to select.
1. Be sure the the checkbox next to "requirements.txt" is selected. 1. Be sure the checkbox next to "requirements.txt" is selected.
1. Click OK. 1. Click OK.
### Step 6: Fire up the website ### Step 6: Fire up the website
1. Go to Terminial -> New Terminal
1. Go to Terminal -> New Terminal
1. type: **sphinx-autobuild . _build/html** at the prompt and hit enter. 1. type: **sphinx-autobuild . _build/html** at the prompt and hit enter.
1. Open your browser and go to http://127.0.0.1:8000 1. Open your browser and go to [http://127.0.0.1:8000](http://127.0.0.1:8000).
### Step 7: Make a chance ### Step 7: Make a change
1. Open up a markdown file, and make a change. 1. Open up a markdown file, and make a change.
### Step 8: Commit your changes and push them up for everyone. ### Step 8: Commit your changes and push them up for everyone
1. Select the "git" button on the left hand side of the toolbar (cricles with lines between them) ![Git button](./images/git.png "Git button")
1. Select the "git" button on the left hand side of the toolbar (circles with lines between them) ![Git button](./images/git.png "Git button")
2. Press the blue "Commit" button. 2. Press the blue "Commit" button.
3. Any changes you pushed up, should be live on our website within 5 to 10 minutes. 3. Any changes you pushed up should be live on our website within 5 to 10 minutes.
## Linting
```{admonition} Linting is just an idea
:class: warning
Documentation people: please ignore this for now.
```
We may decide to check the documentation with a "linter" which is designed to keep the documentation consistent and standardized.
One option is [markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli), which uses David Anson's [NodeJS-based markdownlint](https://github.com/DavidAnson/markdownlint), which these days seems to be more popular than the [ruby-based markdownlint](https://github.com/markdownlint/markdownlint).
A `.markdownlint.jsonc` file has been added that configures the same markdownlint program (basically to ignore the rule about long lines, since we are using ventilated prose).

View File

@ -17,6 +17,7 @@ To access SpiffWorkflow, simply sign in using your Keycloak account. Once you ha
:alt: Login Page :alt: Login Page
:width: 45% :width: 45%
``` ```
```{image} images/Untitled_1.png ```{image} images/Untitled_1.png
:alt: Home Page :alt: Home Page
:width: 45% :width: 45%
@ -60,6 +61,7 @@ The process section provides a comprehensive view of the process ecosystem by sh
:class: info :class: info
💡 A **process group** is a way of grouping a bunch of **process models.** A **process model** contains all the files necessary to execute a specific process. 💡 A **process group** is a way of grouping a bunch of **process models.** A **process model** contains all the files necessary to execute a specific process.
``` ```
-- --
![Untitled](images/Untitled_4.png) ![Untitled](images/Untitled_4.png)
@ -142,7 +144,7 @@ After starting a process, it's important to stay informed about its progress. Ev
Here's how you can view the steps of the process you just started. Here's how you can view the steps of the process you just started.
### Step 1: Navigate to the “Home” or “Process Instance” section. ### Step 1: Navigate to the “Home” or “Process Instance” section
There are 2 ways of finding your process instances. There are 2 ways of finding your process instances.
@ -170,7 +172,7 @@ The Process-defined **metadata can provide valuable insights into its history, c
To check the metadata of a process instance, follow these steps. To check the metadata of a process instance, follow these steps.
### Step 1: Navigate to the “Home” or “Process Instance” section. ### Step 1: Navigate to the “Home” or “Process Instance” section as before
Once you're signed in, navigate to the home section. Here you will find a list of all the process instances you've initiated under **“Started by me”**. Once you're signed in, navigate to the home section. Here you will find a list of all the process instances you've initiated under **“Started by me”**.
@ -231,7 +233,7 @@ To filter the list, click on the "Filter" option. This will expand the filter se
![Untitled](images/Untitled_20.png) ![Untitled](images/Untitled_20.png)
### Step 3: Apply Filters: ### Step 3: Apply Filters
Once you have entered all the relevant filter details, click on the "**Apply**" button to apply the filters. The system will then display all the process instances matching the input details. Once you have entered all the relevant filter details, click on the "**Apply**" button to apply the filters. The system will then display all the process instances matching the input details.
@ -303,4 +305,4 @@ Ensure that all required details have been included such as Process name, Proces
![Untitled](images/Untitled_32.png) ![Untitled](images/Untitled_32.png)
By following these steps, you can request the special permissions needed to carry out your tasks effectively. By following these steps, you can request the special permissions needed to carry out your tasks effectively.

View File

@ -7,5 +7,20 @@ function error_handler() {
trap 'error_handler ${LINENO} $?' ERR trap 'error_handler ${LINENO} $?' ERR
set -o errtrace -o errexit -o nounset -o pipefail set -o errtrace -o errexit -o nounset -o pipefail
set -x database=spiffworkflow_backend_local_development
mysql -uroot spiffworkflow_backend_development -e 'select pa.id, g.identifier group_identifier, pt.uri, permission from permission_assignment pa join principal p on p.id = pa.principal_id join `group` g on g.id = p.group_id join permission_target pt on pt.id = pa.permission_target_id;' if [[ "${1:-}" == "test" ]]; then
database=spiffworkflow_backend_unit_testing
fi
# shellcheck disable=2016
mysql -uroot "$database" -e '
select u.username user, g.identifier group
FROM `user` u
JOIN `user_group_assignment` uga on uga.user_id = u.id
JOIN `group` g on g.id = uga.group_id;
select pa.id, g.identifier group_identifier, pt.uri, permission from permission_assignment pa
join principal p on p.id = pa.principal_id
join `group` g on g.id = p.group_id
join permission_target pt on pt.id = pa.permission_target_id;
'

View File

@ -4,19 +4,12 @@ import shutil
import pytest import pytest
from flask.app import Flask from flask.app import Flask
from flask.testing import FlaskClient
from tests.spiffworkflow_backend.helpers.base_test import BaseTest from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from spiffworkflow_backend.models.bpmn_process import BpmnProcessModel from spiffworkflow_backend.models.bpmn_process import BpmnProcessModel
from spiffworkflow_backend.models.db import db from spiffworkflow_backend.models.db import db
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
from spiffworkflow_backend.models.user import UserModel from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.process_instance_processor import ( from spiffworkflow_backend.services.authorization_service import AuthorizationService
ProcessInstanceProcessor,
)
from spiffworkflow_backend.services.process_instance_service import (
ProcessInstanceService,
)
from spiffworkflow_backend.services.process_model_service import ProcessModelService from spiffworkflow_backend.services.process_model_service import ProcessModelService
@ -62,64 +55,6 @@ def with_db_and_bpmn_file_cleanup() -> None:
@pytest.fixture() @pytest.fixture()
def with_super_admin_user() -> UserModel: def with_super_admin_user() -> UserModel:
"""With_super_admin_user.""" """With_super_admin_user."""
return BaseTest.create_user_with_permission("super_admin") user = BaseTest.find_or_create_user(username="testadmin1")
AuthorizationService.import_permissions_from_yaml_file(user)
return user
@pytest.fixture()
def setup_process_instances_for_reports(
client: FlaskClient, with_super_admin_user: UserModel
) -> list[ProcessInstanceModel]:
"""Setup_process_instances_for_reports."""
user = with_super_admin_user
process_group_id = "runs_without_input"
process_model_id = "sample"
# bpmn_file_name = "sample.bpmn"
bpmn_file_location = "sample"
process_model_identifier = BaseTest().create_group_and_model_with_bpmn(
client,
with_super_admin_user,
process_group_id=process_group_id,
process_model_id=process_model_id,
# bpmn_file_name=bpmn_file_name,
bpmn_file_location=bpmn_file_location,
)
# BaseTest().create_process_group(
# client=client, user=user, process_group_id=process_group_id, display_name=process_group_id
# )
# process_model_id = "runs_without_input/sample"
# load_test_spec(
# process_model_id=f"{process_group_id}/{process_model_id}",
# process_model_source_directory="sample"
# )
process_instances = []
for data in [kay(), ray(), jay()]:
process_instance = ProcessInstanceService.create_process_instance_from_process_model_identifier(
# process_group_identifier=process_group_id,
process_model_identifier=process_model_identifier,
user=user,
)
processor = ProcessInstanceProcessor(process_instance)
processor.slam_in_data(data)
process_instance.status = "complete"
db.session.add(process_instance)
db.session.commit()
process_instances.append(process_instance)
return process_instances
def kay() -> dict:
"""Kay."""
return {"name": "kay", "grade_level": 2, "test_score": 10}
def ray() -> dict:
"""Ray."""
return {"name": "ray", "grade_level": 1, "test_score": 9}
def jay() -> dict:
"""Jay."""
return {"name": "jay", "grade_level": 2, "test_score": 8}

View File

@ -41,6 +41,18 @@ def setup_database(session: Session) -> None:
session.env[flask_env_key] = "e7711a3ba96c46c68e084a86952de16f" session.env[flask_env_key] = "e7711a3ba96c46c68e084a86952de16f"
session.env["FLASK_APP"] = "src/spiffworkflow_backend" session.env["FLASK_APP"] = "src/spiffworkflow_backend"
session.env["SPIFFWORKFLOW_BACKEND_ENV"] = "unit_testing" session.env["SPIFFWORKFLOW_BACKEND_ENV"] = "unit_testing"
if os.environ.get("SPIFFWORKFLOW_BACKEND_DATABASE_TYPE") == "sqlite":
# maybe replace this sqlite-specific block with ./bin/recreate_db clean rmall
# (if we can make it work, since it uses poetry),
# which would also remove the migrations folder and re-create things as a single migration
if os.path.exists("migrations"):
import shutil
shutil.rmtree("migrations")
for task in ["init", "migrate"]:
session.run("flask", "db", task)
session.run("flask", "db", "upgrade") session.run("flask", "db", "upgrade")

File diff suppressed because it is too large Load Diff

View File

@ -31,7 +31,7 @@ SpiffWorkflow = {git = "https://github.com/sartography/SpiffWorkflow", rev = "ma
# SpiffWorkflow = {git = "https://github.com/sartography/SpiffWorkflow", rev = "6cad2981712bb61eca23af1adfafce02d3277cb9"} # SpiffWorkflow = {git = "https://github.com/sartography/SpiffWorkflow", rev = "6cad2981712bb61eca23af1adfafce02d3277cb9"}
# SpiffWorkflow = {develop = true, path = "../../spiffworkflow/" } # SpiffWorkflow = {develop = true, path = "../../spiffworkflow/" }
sentry-sdk = "^1.10" sentry-sdk = "^1.10"
sphinx-autoapi = "^2.0" # sphinx-autoapi = "^2.0"
mysql-connector-python = "*" mysql-connector-python = "*"
pytest-flask = "^1.2.0" pytest-flask = "^1.2.0"
pytest-flask-sqlalchemy = "^1.1.0" pytest-flask-sqlalchemy = "^1.1.0"
@ -93,8 +93,8 @@ safety = "^2.3.1"
mypy = ">=0.961" mypy = ">=0.961"
typeguard = "^3" typeguard = "^3"
xdoctest = {extras = ["colors"], version = "^1.0.1"} xdoctest = {extras = ["colors"], version = "^1.0.1"}
sphinx = "^5.0.2" # sphinx = "^5.0.2"
sphinx-autobuild = ">=2021.3.14" # sphinx-autobuild = ">=2021.3.14"
pre-commit = "^2.20.0" pre-commit = "^2.20.0"
flake8 = "^4.0.1" flake8 = "^4.0.1"
black = ">=21.10b0" black = ">=21.10b0"
@ -111,10 +111,10 @@ pep8-naming = "^0.13.2"
darglint = "^1.8.1" darglint = "^1.8.1"
reorder-python-imports = "^3.9.0" reorder-python-imports = "^3.9.0"
pre-commit-hooks = "^4.0.1" pre-commit-hooks = "^4.0.1"
sphinx-click = "^4.3.0" # sphinx-click = "^4.3.0"
Pygments = "^2.10.0" Pygments = "^2.10.0"
pyupgrade = "^3.1.0" pyupgrade = "^3.1.0"
furo = ">=2021.11.12" # furo = ">=2021.11.12"
[tool.poetry.scripts] [tool.poetry.scripts]
spiffworkflow-backend = "spiffworkflow_backend.__main__:main" spiffworkflow-backend = "spiffworkflow_backend.__main__:main"

View File

@ -149,7 +149,7 @@ paths:
$ref: "#/components/schemas/OkTrue" $ref: "#/components/schemas/OkTrue"
/debug/test-raise-error: /debug/test-raise-error:
get: post:
operationId: spiffworkflow_backend.routes.debug_controller.test_raise_error operationId: spiffworkflow_backend.routes.debug_controller.test_raise_error
summary: Returns an unhandled exception that should notify sentry, if sentry is configured summary: Returns an unhandled exception that should notify sentry, if sentry is configured
tags: tags:
@ -184,7 +184,7 @@ paths:
description: The identifier for the last visited page for the user. description: The identifier for the last visited page for the user.
schema: schema:
type: string type: string
get: post:
tags: tags:
- Active User - Active User
operationId: spiffworkflow_backend.routes.active_users_controller.active_user_updates operationId: spiffworkflow_backend.routes.active_users_controller.active_user_updates
@ -207,7 +207,7 @@ paths:
description: The identifier for the last visited page for the user. description: The identifier for the last visited page for the user.
schema: schema:
type: string type: string
get: post:
tags: tags:
- Active User - Active User
operationId: spiffworkflow_backend.routes.active_users_controller.active_user_unregister operationId: spiffworkflow_backend.routes.active_users_controller.active_user_unregister
@ -425,7 +425,7 @@ paths:
schema: schema:
$ref: "#/components/schemas/ProcessModel" $ref: "#/components/schemas/ProcessModel"
/process-models-natural-language/{modified_process_group_id}: /process-model-natural-language/{modified_process_group_id}:
parameters: parameters:
- name: modified_process_group_id - name: modified_process_group_id
in: path in: path
@ -564,7 +564,7 @@ paths:
schema: schema:
$ref: "#/components/schemas/ProcessModel" $ref: "#/components/schemas/ProcessModel"
/process-models/{modified_process_model_identifier}/publish: /process-model-publish/{modified_process_model_identifier}:
parameters: parameters:
- name: modified_process_model_identifier - name: modified_process_model_identifier
in: path in: path
@ -1122,7 +1122,7 @@ paths:
- Process Instances - Process Instances
responses: responses:
"200": "200":
description: Empty ok true response on successful resume. description: Empty ok true response on successful reset.
content: content:
application/json: application/json:
schema: schema:

View File

@ -143,6 +143,7 @@ SPIFFWORKFLOW_BACKEND_ALLOW_CONFISCATING_LOCK_AFTER_SECONDS = int(
environ.get("SPIFFWORKFLOW_BACKEND_ALLOW_CONFISCATING_LOCK_AFTER_SECONDS", default="600") environ.get("SPIFFWORKFLOW_BACKEND_ALLOW_CONFISCATING_LOCK_AFTER_SECONDS", default="600")
) )
# FIXME: do not default this but we will need to coordinate release of it since it is a breaking change
SPIFFWORKFLOW_BACKEND_DEFAULT_USER_GROUP = environ.get("SPIFFWORKFLOW_BACKEND_DEFAULT_USER_GROUP", default="everybody") SPIFFWORKFLOW_BACKEND_DEFAULT_USER_GROUP = environ.get("SPIFFWORKFLOW_BACKEND_DEFAULT_USER_GROUP", default="everybody")
SPIFFWORKFLOW_BACKEND_ENGINE_STEP_DEFAULT_STRATEGY_BACKGROUND = environ.get( SPIFFWORKFLOW_BACKEND_ENGINE_STEP_DEFAULT_STRATEGY_BACKGROUND = environ.get(

View File

@ -12,6 +12,5 @@ groups:
permissions: permissions:
admin: admin:
groups: [admin] groups: [admin]
users: []
allowed_permissions: [create, read, update, delete] allowed_permissions: [create, read, update, delete]
uri: /* uri: /*

View File

@ -1,5 +1,3 @@
default_group: everybody
groups: groups:
admin: admin:
users: users:
@ -19,6 +17,5 @@ groups:
permissions: permissions:
admin: admin:
groups: [admin, tech_writers] groups: [admin, tech_writers]
users: []
allowed_permissions: [create, read, update, delete] allowed_permissions: [create, read, update, delete]
uri: /* uri: /*

View File

@ -1,4 +1,3 @@
default_group: everybody
users: users:
admin: admin:
@ -41,52 +40,43 @@ permissions:
# Admins have access to everything. # Admins have access to everything.
admin: admin:
groups: [admin] groups: [admin]
users: []
allowed_permissions: [create, read, update, delete] allowed_permissions: [create, read, update, delete]
uri: /* uri: /*
# Everybody can participate in tasks assigned to them. # Everybody can participate in tasks assigned to them.
tasks-crud: tasks-crud:
groups: [everybody] groups: [everybody]
users: []
allowed_permissions: [create, read, update, delete] allowed_permissions: [create, read, update, delete]
uri: /tasks/* uri: /tasks/*
# Everybody can start all intstances # Everybody can start all intstances
create-test-instances: create-test-instances:
groups: [ everybody ] groups: [ everybody ]
users: [ ]
allowed_permissions: [ create ] allowed_permissions: [ create ]
uri: /process-instances/* uri: /process-instances/*
# Everyone can see everything (all groups, and processes are visible) # Everyone can see everything (all groups, and processes are visible)
read-all-process-groups: read-all-process-groups:
groups: [ everybody ] groups: [ everybody ]
users: [ ]
allowed_permissions: [ read ] allowed_permissions: [ read ]
uri: /process-groups/* uri: /process-groups/*
read-all-process-models: read-all-process-models:
groups: [ everybody ] groups: [ everybody ]
users: [ ]
allowed_permissions: [ read ] allowed_permissions: [ read ]
uri: /process-models/* uri: /process-models/*
read-all-process-instance: read-all-process-instance:
groups: [ everybody ] groups: [ everybody ]
users: [ ]
allowed_permissions: [ read ] allowed_permissions: [ read ]
uri: /process-instances/* uri: /process-instances/*
read-process-instance-reports: read-process-instance-reports:
groups: [ everybody ] groups: [ everybody ]
users: [ ]
allowed_permissions: [ read ] allowed_permissions: [ read ]
uri: /process-instances/reports/* uri: /process-instances/reports/*
processes-read: processes-read:
groups: [ everybody ] groups: [ everybody ]
users: [ ]
allowed_permissions: [ read ] allowed_permissions: [ read ]
uri: /processes uri: /processes
groups-everybody: groups-everybody:
groups: [everybody] groups: [everybody]
users: []
allowed_permissions: [create, read] allowed_permissions: [create, read]
uri: /v1.0/user-groups/for-current-user uri: /v1.0/user-groups/for-current-user

View File

@ -0,0 +1,17 @@
groups:
admin:
users: [admin@spiffworkflow.org]
permissions:
process-groups-ro:
groups: [admin]
allowed_permissions: [read]
uri: PG:ALL
basic:
groups: [admin]
allowed_permissions: [all]
uri: BASIC
elevated-operations:
groups: [admin]
allowed_permissions: [all]
uri: ELEVATED

View File

@ -0,0 +1,21 @@
groups:
admin:
users: [admin@spiffworkflow.org]
permissions:
process-groups-ro:
groups: [admin]
allowed_permissions: [read]
uri: PG:ALL
basic:
groups: [admin]
allowed_permissions: [all]
uri: BASIC
elevated-operations:
groups: [admin]
allowed_permissions: [all]
uri: ELEVATED
process-model-publish:
groups: [admin]
allowed_permissions: [create]
uri: /process-model-publish/*

View File

@ -1,84 +0,0 @@
default_group: everybody
groups:
admin:
users: [admin@spiffworkflow.org]
permissions:
admin:
groups: [admin]
users: []
allowed_permissions: [read]
uri: /*
tasks-crud:
groups: [admin]
users: []
allowed_permissions: [create, update, delete]
uri: /tasks/*
process-instances-crud:
groups: [ admin ]
users: [ ]
allowed_permissions: [create, update, delete]
uri: /process-instances/*
suspend:
groups: [admin]
users: []
allowed_permissions: [create]
uri: /v1.0/process-instance-suspend
terminate:
groups: [admin]
users: []
allowed_permissions: [create]
uri: /v1.0/process-instance-terminate
resume:
groups: [admin]
users: []
allowed_permissions: [create]
uri: /v1.0/process-instance-resume
reset:
groups: [admin]
users: []
allowed_permissions: [create]
uri: /v1.0/process-instance-reset
users-exist:
groups: [admin]
users: []
allowed_permissions: [create]
uri: /v1.0/users/exists/by-username
send-event:
groups: [admin]
users: []
allowed_permissions: [create]
uri: /v1.0/send-event/*
task-complete:
groups: [admin]
users: []
allowed_permissions: [create]
uri: /v1.0/task-complete/*
messages:
groups: [admin]
users: []
allowed_permissions: [create]
uri: /v1.0/messages/*
secrets:
groups: [admin]
users: []
allowed_permissions: [create, update, delete]
uri: /v1.0/secrets/*
task-data:
groups: [admin]
users: []
allowed_permissions: [update]
uri: /v1.0/task-data/*

View File

@ -1,4 +1,3 @@
default_group: everybody
groups: groups:
admin: admin:
@ -11,6 +10,5 @@ groups:
permissions: permissions:
admin: admin:
groups: [admin, group1, group2] groups: [admin, group1, group2]
users: []
allowed_permissions: [create, read, update, delete] allowed_permissions: [create, read, update, delete]
uri: /* uri: /*

View File

@ -1,4 +1,3 @@
default_group: everybody
groups: groups:
admin: admin:
@ -7,6 +6,5 @@ groups:
permissions: permissions:
admin: admin:
groups: [admin] groups: [admin]
users: []
allowed_permissions: [create, read, update, delete] allowed_permissions: [create, read, update, delete]
uri: /* uri: /*

View File

@ -1,5 +1,3 @@
default_group: everybody
users: users:
testadmin1: testadmin1:
service: https://testing/openid/thing service: https://testing/openid/thing
@ -18,51 +16,50 @@ groups:
users: [testuser2, testuser3, testuser4] users: [testuser2, testuser3, testuser4]
permissions: permissions:
admin: process-groups-all:
groups: [admin] groups: [admin]
users: [] allowed_permissions: [all]
allowed_permissions: [create, read, update, delete] uri: PG:ALL
uri: /* basic:
groups: [admin]
allowed_permissions: [all]
uri: BASIC
elevated-operations:
groups: [admin]
allowed_permissions: [all]
uri: ELEVATED
read-all: read-all:
groups: ["Finance Team", hr, admin] groups: ["Finance Team", hr, admin]
users: []
allowed_permissions: [read] allowed_permissions: [read]
uri: /* uri: /*
process-instances-find-by-id: process-instances-find-by-id:
groups: [everybody] groups: [everybody]
users: []
allowed_permissions: [read] allowed_permissions: [read]
uri: /process-instances/find-by-id/* uri: /process-instances/find-by-id/*
tasks-crud: tasks-crud:
groups: [everybody] groups: [everybody]
users: []
allowed_permissions: [create, read, update, delete] allowed_permissions: [create, read, update, delete]
uri: /tasks/* uri: /tasks/*
# TODO: all uris should really have the same structure
finance-admin-group: finance-admin-group:
groups: ["Finance Team"] groups: ["Finance Team"]
users: [testuser4] allowed_permissions: [all]
allowed_permissions: [create, read, update, delete] uri: PG:finance
uri: /process-groups/finance/*
finance-admin-model: finance-hr-start:
groups: ["Finance Team"] groups: ["hr"]
users: [testuser4] allowed_permissions: [start]
allowed_permissions: [create, read, update, delete] uri: PG:finance
uri: /process-models/finance/*
finance-admin-model-lanes: finance-admin-model-lanes:
groups: ["Finance Team"] groups: ["Finance Team"]
users: [testuser4]
allowed_permissions: [create, read, update, delete] allowed_permissions: [create, read, update, delete]
uri: /process-models/finance:model_with_lanes/* uri: /process-models/finance:model_with_lanes/*
finance-admin-instance-run: finance-admin-instance-run:
groups: ["Finance Team"] groups: ["Finance Team"]
users: [testuser4]
allowed_permissions: [create, read, update, delete] allowed_permissions: [create, read, update, delete]
uri: /process-instances/* uri: /process-instances/*

View File

@ -257,7 +257,7 @@ def manual_complete_task(
process_instance = ProcessInstanceModel.query.filter(ProcessInstanceModel.id == int(process_instance_id)).first() process_instance = ProcessInstanceModel.query.filter(ProcessInstanceModel.id == int(process_instance_id)).first()
if process_instance: if process_instance:
processor = ProcessInstanceProcessor(process_instance) processor = ProcessInstanceProcessor(process_instance)
processor.manual_complete_task(task_guid, execute) processor.manual_complete_task(task_guid, execute, g.user)
else: else:
raise ApiError( raise ApiError(
error_code="complete_task", error_code="complete_task",

View File

@ -34,6 +34,5 @@ class RefreshPermissions(Script):
*args: Any, *args: Any,
**kwargs: Any, **kwargs: Any,
) -> Any: ) -> Any:
"""Run."""
group_info = args[0] group_info = args[0]
AuthorizationService.refresh_permissions(group_info) AuthorizationService.refresh_permissions(group_info)

View File

@ -1,43 +0,0 @@
"""Save process instance metadata."""
from typing import Any
from spiffworkflow_backend.models.db import db
from spiffworkflow_backend.models.process_instance_metadata import (
ProcessInstanceMetadataModel,
)
from spiffworkflow_backend.models.script_attributes_context import (
ScriptAttributesContext,
)
from spiffworkflow_backend.scripts.script import Script
class SaveProcessInstanceMetadata(Script):
"""SaveProcessInstanceMetadata."""
def get_description(self) -> str:
"""Get_description."""
return """Save a given dict as process instance metadata (useful for creating reports)."""
def run(
self,
script_attributes_context: ScriptAttributesContext,
*args: Any,
**kwargs: Any,
) -> Any:
"""Run."""
metadata_dict = args[0]
if script_attributes_context.process_instance_id is None:
raise self.get_proces_instance_id_is_missing_error("save_process_instance_metadata")
for key, value in metadata_dict.items():
pim = ProcessInstanceMetadataModel.query.filter_by(
process_instance_id=script_attributes_context.process_instance_id,
key=key,
).first()
if pim is None:
pim = ProcessInstanceMetadataModel(
process_instance_id=script_attributes_context.process_instance_id,
key=key,
)
pim.value = value
db.session.add(pim)
db.session.commit()

View File

@ -1,11 +1,9 @@
"""Authorization_service."""
import inspect import inspect
import re import re
from dataclasses import dataclass from dataclasses import dataclass
from hashlib import sha256 from hashlib import sha256
from hmac import compare_digest from hmac import compare_digest
from hmac import HMAC from hmac import HMAC
from typing import Any
from typing import Optional from typing import Optional
from typing import Set from typing import Set
from typing import TypedDict from typing import TypedDict
@ -29,7 +27,6 @@ from spiffworkflow_backend.models.permission_target import PermissionTargetModel
from spiffworkflow_backend.models.principal import MissingPrincipalError from spiffworkflow_backend.models.principal import MissingPrincipalError
from spiffworkflow_backend.models.principal import PrincipalModel from spiffworkflow_backend.models.principal import PrincipalModel
from spiffworkflow_backend.models.user import UserModel from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.models.user import UserNotFoundError
from spiffworkflow_backend.models.user_group_assignment import UserGroupAssignmentModel from spiffworkflow_backend.models.user_group_assignment import UserGroupAssignmentModel
from spiffworkflow_backend.routes.openid_blueprint import openid_blueprint from spiffworkflow_backend.routes.openid_blueprint import openid_blueprint
from spiffworkflow_backend.services.authentication_service import NotAuthorizedError from spiffworkflow_backend.services.authentication_service import NotAuthorizedError
@ -42,25 +39,23 @@ from spiffworkflow_backend.services.user_service import UserService
class PermissionsFileNotSetError(Exception): class PermissionsFileNotSetError(Exception):
"""PermissionsFileNotSetError.""" pass
class HumanTaskNotFoundError(Exception): class HumanTaskNotFoundError(Exception):
"""HumanTaskNotFoundError.""" pass
class UserDoesNotHaveAccessToTaskError(Exception): class UserDoesNotHaveAccessToTaskError(Exception):
"""UserDoesNotHaveAccessToTaskError.""" pass
class InvalidPermissionError(Exception): class InvalidPermissionError(Exception):
"""InvalidPermissionError.""" pass
@dataclass @dataclass
class PermissionToAssign: class PermissionToAssign:
"""PermissionToAssign."""
permission: str permission: str
target_uri: str target_uri: str
@ -80,10 +75,12 @@ PATH_SEGMENTS_FOR_PERMISSION_ALL = [
"path": "/process-instances", "path": "/process-instances",
"relevant_permissions": ["create", "read", "delete"], "relevant_permissions": ["create", "read", "delete"],
}, },
{"path": "/process-instance-suspend", "relevant_permissions": ["create"]},
{"path": "/process-instance-terminate", "relevant_permissions": ["create"]},
{"path": "/process-data", "relevant_permissions": ["read"]}, {"path": "/process-data", "relevant_permissions": ["read"]},
{"path": "/process-data-file-download", "relevant_permissions": ["read"]}, {"path": "/process-data-file-download", "relevant_permissions": ["read"]},
{"path": "/process-instance-suspend", "relevant_permissions": ["create"]},
{"path": "/process-instance-terminate", "relevant_permissions": ["create"]},
{"path": "/process-model-natural-language", "relevant_permissions": ["create"]},
{"path": "/process-model-publish", "relevant_permissions": ["create"]},
{"path": "/task-data", "relevant_permissions": ["read", "update"]}, {"path": "/task-data", "relevant_permissions": ["read", "update"]},
] ]
@ -93,21 +90,29 @@ class UserToGroupDict(TypedDict):
group_identifier: str group_identifier: str
class DesiredPermissionDict(TypedDict): class AddedPermissionDict(TypedDict):
"""DesiredPermissionDict."""
group_identifiers: Set[str] group_identifiers: Set[str]
permission_assignments: list[PermissionAssignmentModel] permission_assignments: list[PermissionAssignmentModel]
user_to_group_identifiers: list[UserToGroupDict] user_to_group_identifiers: list[UserToGroupDict]
class DesiredGroupPermissionDict(TypedDict):
actions: list[str]
uri: str
class GroupPermissionsDict(TypedDict):
users: list[str]
name: str
permissions: list[DesiredGroupPermissionDict]
class AuthorizationService: class AuthorizationService:
"""Determine whether a user has permission to perform their request.""" """Determine whether a user has permission to perform their request."""
# https://stackoverflow.com/a/71320673/6090676 # https://stackoverflow.com/a/71320673/6090676
@classmethod @classmethod
def verify_sha256_token(cls, auth_header: Optional[str]) -> None: def verify_sha256_token(cls, auth_header: Optional[str]) -> None:
"""Verify_sha256_token."""
if auth_header is None: if auth_header is None:
raise TokenNotProvidedError( raise TokenNotProvidedError(
"unauthorized", "unauthorized",
@ -123,7 +128,6 @@ class AuthorizationService:
@classmethod @classmethod
def has_permission(cls, principals: list[PrincipalModel], permission: str, target_uri: str) -> bool: def has_permission(cls, principals: list[PrincipalModel], permission: str, target_uri: str) -> bool:
"""Has_permission."""
principal_ids = [p.id for p in principals] principal_ids = [p.id for p in principals]
target_uri_normalized = target_uri.removeprefix(V1_API_PATH_PREFIX) target_uri_normalized = target_uri.removeprefix(V1_API_PATH_PREFIX)
@ -153,7 +157,6 @@ class AuthorizationService:
@classmethod @classmethod
def user_has_permission(cls, user: UserModel, permission: str, target_uri: str) -> bool: def user_has_permission(cls, user: UserModel, permission: str, target_uri: str) -> bool:
"""User_has_permission."""
if user.principal is None: if user.principal is None:
raise MissingPrincipalError(f"Missing principal for user with id: {user.id}") raise MissingPrincipalError(f"Missing principal for user with id: {user.id}")
@ -179,7 +182,6 @@ class AuthorizationService:
@classmethod @classmethod
def associate_user_with_group(cls, user: UserModel, group: GroupModel) -> None: def associate_user_with_group(cls, user: UserModel, group: GroupModel) -> None:
"""Associate_user_with_group."""
user_group_assignemnt = UserGroupAssignmentModel.query.filter_by(user_id=user.id, group_id=group.id).first() user_group_assignemnt = UserGroupAssignmentModel.query.filter_by(user_id=user.id, group_id=group.id).first()
if user_group_assignemnt is None: if user_group_assignemnt is None:
user_group_assignemnt = UserGroupAssignmentModel(user_id=user.id, group_id=group.id) user_group_assignemnt = UserGroupAssignmentModel(user_id=user.id, group_id=group.id)
@ -187,88 +189,13 @@ class AuthorizationService:
db.session.commit() db.session.commit()
@classmethod @classmethod
def import_permissions_from_yaml_file(cls, raise_if_missing_user: bool = False) -> DesiredPermissionDict: def import_permissions_from_yaml_file(cls, user_model: Optional[UserModel] = None) -> AddedPermissionDict:
"""Import_permissions_from_yaml_file.""" group_permissions = cls.parse_permissions_yaml_into_group_info()
if current_app.config["SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME"] is None: result = cls.add_permissions_from_group_permissions(group_permissions, user_model)
raise ( return result
PermissionsFileNotSetError(
"SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME needs to be set in order to import permissions"
)
)
permission_configs = None
with open(current_app.config["SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_ABSOLUTE_PATH"]) as file:
permission_configs = yaml.safe_load(file)
default_group = None
unique_user_group_identifiers: Set[str] = set()
user_to_group_identifiers: list[UserToGroupDict] = []
if "default_group" in permission_configs:
default_group_identifier = permission_configs["default_group"]
default_group = GroupService.find_or_create_group(default_group_identifier)
unique_user_group_identifiers.add(default_group_identifier)
if "groups" in permission_configs:
for group_identifier, group_config in permission_configs["groups"].items():
group = GroupService.find_or_create_group(group_identifier)
unique_user_group_identifiers.add(group_identifier)
for username in group_config["users"]:
user = UserModel.query.filter_by(username=username).first()
if user is None:
if raise_if_missing_user:
raise (UserNotFoundError(f"Could not find a user with name: {username}"))
continue
user_to_group_dict: UserToGroupDict = {
"username": user.username,
"group_identifier": group_identifier,
}
user_to_group_identifiers.append(user_to_group_dict)
cls.associate_user_with_group(user, group)
permission_assignments = []
if "permissions" in permission_configs:
for _permission_identifier, permission_config in permission_configs["permissions"].items():
uri = permission_config["uri"]
permission_target = cls.find_or_create_permission_target(uri)
for allowed_permission in permission_config["allowed_permissions"]:
if "groups" in permission_config:
for group_identifier in permission_config["groups"]:
group = GroupService.find_or_create_group(group_identifier)
unique_user_group_identifiers.add(group_identifier)
permission_assignments.append(
cls.create_permission_for_principal(
group.principal,
permission_target,
allowed_permission,
)
)
if "users" in permission_config:
for username in permission_config["users"]:
user = UserModel.query.filter_by(username=username).first()
if user is not None:
principal = (
PrincipalModel.query.join(UserModel).filter(UserModel.username == username).first()
)
permission_assignments.append(
cls.create_permission_for_principal(
principal, permission_target, allowed_permission
)
)
if default_group is not None:
for user in UserModel.query.all():
cls.associate_user_with_group(user, default_group)
return {
"group_identifiers": unique_user_group_identifiers,
"permission_assignments": permission_assignments,
"user_to_group_identifiers": user_to_group_identifiers,
}
@classmethod @classmethod
def find_or_create_permission_target(cls, uri: str) -> PermissionTargetModel: def find_or_create_permission_target(cls, uri: str) -> PermissionTargetModel:
"""Find_or_create_permission_target."""
uri_with_percent = re.sub(r"\*", "%", uri) uri_with_percent = re.sub(r"\*", "%", uri)
target_uri_normalized = uri_with_percent.removeprefix(V1_API_PATH_PREFIX) target_uri_normalized = uri_with_percent.removeprefix(V1_API_PATH_PREFIX)
permission_target: Optional[PermissionTargetModel] = PermissionTargetModel.query.filter_by( permission_target: Optional[PermissionTargetModel] = PermissionTargetModel.query.filter_by(
@ -287,7 +214,6 @@ class AuthorizationService:
permission_target: PermissionTargetModel, permission_target: PermissionTargetModel,
permission: str, permission: str,
) -> PermissionAssignmentModel: ) -> PermissionAssignmentModel:
"""Create_permission_for_principal."""
permission_assignment: Optional[PermissionAssignmentModel] = PermissionAssignmentModel.query.filter_by( permission_assignment: Optional[PermissionAssignmentModel] = PermissionAssignmentModel.query.filter_by(
principal_id=principal.id, principal_id=principal.id,
permission_target_id=permission_target.id, permission_target_id=permission_target.id,
@ -306,7 +232,6 @@ class AuthorizationService:
@classmethod @classmethod
def should_disable_auth_for_request(cls) -> bool: def should_disable_auth_for_request(cls) -> bool:
"""Should_disable_auth_for_request."""
swagger_functions = ["get_json_spec"] swagger_functions = ["get_json_spec"]
authentication_exclusion_list = [ authentication_exclusion_list = [
"status", "status",
@ -344,7 +269,6 @@ class AuthorizationService:
@classmethod @classmethod
def get_permission_from_http_method(cls, http_method: str) -> Optional[str]: def get_permission_from_http_method(cls, http_method: str) -> Optional[str]:
"""Get_permission_from_request_method."""
request_method_mapper = { request_method_mapper = {
"POST": "create", "POST": "create",
"GET": "read", "GET": "read",
@ -363,7 +287,6 @@ class AuthorizationService:
@classmethod @classmethod
def check_for_permission(cls) -> None: def check_for_permission(cls) -> None:
"""Check_for_permission."""
if cls.should_disable_auth_for_request(): if cls.should_disable_auth_for_request():
return None return None
@ -397,11 +320,6 @@ class AuthorizationService:
@staticmethod @staticmethod
def decode_auth_token(auth_token: str) -> dict[str, Union[str, None]]: def decode_auth_token(auth_token: str) -> dict[str, Union[str, None]]:
"""Decode the auth token.
:param auth_token:
:return: integer|string
"""
secret_key = current_app.config.get("SECRET_KEY") secret_key = current_app.config.get("SECRET_KEY")
if secret_key is None: if secret_key is None:
raise KeyError("we need current_app.config to have a SECRET_KEY") raise KeyError("we need current_app.config to have a SECRET_KEY")
@ -445,10 +363,11 @@ class AuthorizationService:
@classmethod @classmethod
def create_user_from_sign_in(cls, user_info: dict) -> UserModel: def create_user_from_sign_in(cls, user_info: dict) -> UserModel:
"""Create_user_from_sign_in.""" """Fields from user_info.
"""Name, family_name, given_name, middle_name, nickname, preferred_username,"""
"""Profile, picture, website, gender, birthdate, zoneinfo, locale, and updated_at. """ name, family_name, given_name, middle_name, nickname, preferred_username,
"""Email.""" profile, picture, website, gender, birthdate, zoneinfo, locale,updated_at, email.
"""
is_new_user = False is_new_user = False
user_attributes = {} user_attributes = {}
@ -506,7 +425,7 @@ class AuthorizationService:
# we are also a little apprehensive about pre-creating users # we are also a little apprehensive about pre-creating users
# before the user signs in, because we won't know things like # before the user signs in, because we won't know things like
# the external service user identifier. # the external service user identifier.
cls.import_permissions_from_yaml_file() cls.import_permissions_from_yaml_file(user_model)
if is_new_user: if is_new_user:
UserService.add_user_to_human_tasks_if_appropriate(user_model) UserService.add_user_to_human_tasks_if_appropriate(user_model)
@ -521,11 +440,6 @@ class AuthorizationService:
process_related_path_segment: str, process_related_path_segment: str,
target_uris: list[str], target_uris: list[str],
) -> list[PermissionToAssign]: ) -> list[PermissionToAssign]:
"""Get_permissions_to_assign."""
permissions = permission_set.split(",")
if permission_set == "all":
permissions = ["create", "read", "update", "delete"]
permissions_to_assign: list[PermissionToAssign] = [] permissions_to_assign: list[PermissionToAssign] = []
# we were thinking that if you can start an instance, you ought to be able to: # we were thinking that if you can start an instance, you ought to be able to:
@ -556,7 +470,9 @@ class AuthorizationService:
]: ]:
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri=target_uri)) permissions_to_assign.append(PermissionToAssign(permission="read", target_uri=target_uri))
else: else:
permissions = permission_set.split(",")
if permission_set == "all": if permission_set == "all":
permissions = ["create", "read", "update", "delete"]
for path_segment_dict in PATH_SEGMENTS_FOR_PERMISSION_ALL: for path_segment_dict in PATH_SEGMENTS_FOR_PERMISSION_ALL:
target_uri = f"{path_segment_dict['path']}/{process_related_path_segment}" target_uri = f"{path_segment_dict['path']}/{process_related_path_segment}"
relevant_permissions = path_segment_dict["relevant_permissions"] relevant_permissions = path_segment_dict["relevant_permissions"]
@ -571,13 +487,11 @@ class AuthorizationService:
@classmethod @classmethod
def set_basic_permissions(cls) -> list[PermissionToAssign]: def set_basic_permissions(cls) -> list[PermissionToAssign]:
"""Set_basic_permissions."""
permissions_to_assign: list[PermissionToAssign] = [] permissions_to_assign: list[PermissionToAssign] = []
permissions_to_assign.append(PermissionToAssign(permission="create", target_uri="/active-users/*"))
permissions_to_assign.append(PermissionToAssign(permission="create", target_uri="/process-instances/for-me")) permissions_to_assign.append(PermissionToAssign(permission="create", target_uri="/process-instances/for-me"))
permissions_to_assign.append( permissions_to_assign.append(PermissionToAssign(permission="create", target_uri="/users/exists/by-username"))
PermissionToAssign(permission="read", target_uri="/process-instances/report-metadata") permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/connector-proxy/typeahead/*"))
)
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/active-users/*"))
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/debug/version-info")) permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/debug/version-info"))
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/process-groups")) permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/process-groups"))
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/process-models")) permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/process-models"))
@ -585,7 +499,11 @@ class AuthorizationService:
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/processes/callers")) permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/processes/callers"))
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/service-tasks")) permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/service-tasks"))
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/user-groups/for-current-user")) permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/user-groups/for-current-user"))
permissions_to_assign.append(PermissionToAssign(permission="create", target_uri="/users/exists/by-username")) permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/users/search"))
permissions_to_assign.append(
PermissionToAssign(permission="read", target_uri="/process-instances/report-metadata")
)
permissions_to_assign.append( permissions_to_assign.append(
PermissionToAssign(permission="read", target_uri="/process-instances/find-by-id/*") PermissionToAssign(permission="read", target_uri="/process-instances/find-by-id/*")
) )
@ -597,9 +515,37 @@ class AuthorizationService:
permissions_to_assign.append(PermissionToAssign(permission=permission, target_uri="/tasks/*")) permissions_to_assign.append(PermissionToAssign(permission=permission, target_uri="/tasks/*"))
return permissions_to_assign return permissions_to_assign
@classmethod
def set_elevated_permissions(cls) -> list[PermissionToAssign]:
permissions_to_assign: list[PermissionToAssign] = []
for process_instance_action in ["resume", "terminate", "suspend", "reset"]:
permissions_to_assign.append(
PermissionToAssign(permission="create", target_uri=f"/process-instance-{process_instance_action}/*")
)
# FIXME: we need to fix so that user that can start a process-model
# can also start through messages as well
permissions_to_assign.append(PermissionToAssign(permission="create", target_uri="/messages/*"))
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/messages"))
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/authentications"))
permissions_to_assign.append(
PermissionToAssign(permission="create", target_uri="/can-run-privileged-script/*")
)
permissions_to_assign.append(PermissionToAssign(permission="create", target_uri="/debug/*"))
permissions_to_assign.append(PermissionToAssign(permission="create", target_uri="/send-event/*"))
permissions_to_assign.append(PermissionToAssign(permission="create", target_uri="/task-complete/*"))
# read comes from PG and PM permissions
permissions_to_assign.append(PermissionToAssign(permission="update", target_uri="/task-data/*"))
for permission in ["create", "read", "update", "delete"]:
permissions_to_assign.append(PermissionToAssign(permission=permission, target_uri="/process-instances/*"))
permissions_to_assign.append(PermissionToAssign(permission=permission, target_uri="/secrets/*"))
return permissions_to_assign
@classmethod @classmethod
def set_process_group_permissions(cls, target: str, permission_set: str) -> list[PermissionToAssign]: def set_process_group_permissions(cls, target: str, permission_set: str) -> list[PermissionToAssign]:
"""Set_process_group_permissions."""
permissions_to_assign: list[PermissionToAssign] = [] permissions_to_assign: list[PermissionToAssign] = []
process_group_identifier = target.removeprefix("PG:").replace("/", ":").removeprefix(":") process_group_identifier = target.removeprefix("PG:").replace("/", ":").removeprefix(":")
process_related_path_segment = f"{process_group_identifier}:*" process_related_path_segment = f"{process_group_identifier}:*"
@ -616,7 +562,6 @@ class AuthorizationService:
@classmethod @classmethod
def set_process_model_permissions(cls, target: str, permission_set: str) -> list[PermissionToAssign]: def set_process_model_permissions(cls, target: str, permission_set: str) -> list[PermissionToAssign]:
"""Set_process_model_permissions."""
permissions_to_assign: list[PermissionToAssign] = [] permissions_to_assign: list[PermissionToAssign] = []
process_model_identifier = target.removeprefix("PM:").replace("/", ":").removeprefix(":") process_model_identifier = target.removeprefix("PM:").replace("/", ":").removeprefix(":")
process_related_path_segment = f"{process_model_identifier}/*" process_related_path_segment = f"{process_model_identifier}/*"
@ -644,6 +589,8 @@ class AuthorizationService:
* affects given process-model * affects given process-model
BASIC BASIC
* Basic access to complete tasks and use the site * Basic access to complete tasks and use the site
ELEVATED
* Operations that require elevated permissions
Permission Macros: Permission Macros:
all all
@ -666,6 +613,8 @@ class AuthorizationService:
elif target.startswith("BASIC"): elif target.startswith("BASIC"):
permissions_to_assign += cls.set_basic_permissions() permissions_to_assign += cls.set_basic_permissions()
elif target.startswith("ELEVATED"):
permissions_to_assign += cls.set_elevated_permissions()
elif target == "ALL": elif target == "ALL":
for permission in permissions: for permission in permissions:
permissions_to_assign.append(PermissionToAssign(permission=permission, target_uri="/*")) permissions_to_assign.append(PermissionToAssign(permission=permission, target_uri="/*"))
@ -685,7 +634,6 @@ class AuthorizationService:
def add_permission_from_uri_or_macro( def add_permission_from_uri_or_macro(
cls, group_identifier: str, permission: str, target: str cls, group_identifier: str, permission: str, target: str
) -> list[PermissionAssignmentModel]: ) -> list[PermissionAssignmentModel]:
"""Add_permission_from_uri_or_macro."""
group = GroupService.find_or_create_group(group_identifier) group = GroupService.find_or_create_group(group_identifier)
permissions_to_assign = cls.explode_permissions(permission, target) permissions_to_assign = cls.explode_permissions(permission, target)
permission_assignments = [] permission_assignments = []
@ -699,38 +647,112 @@ class AuthorizationService:
return permission_assignments return permission_assignments
@classmethod @classmethod
def refresh_permissions(cls, group_info: list[dict[str, Any]]) -> None: def parse_permissions_yaml_into_group_info(cls) -> list[GroupPermissionsDict]:
"""Adds new permission assignments and deletes old ones.""" if current_app.config["SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME"] is None:
initial_permission_assignments = PermissionAssignmentModel.query.all() raise (
initial_user_to_group_assignments = UserGroupAssignmentModel.query.all() PermissionsFileNotSetError(
result = cls.import_permissions_from_yaml_file() "SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME needs to be set in order to import permissions"
desired_permission_assignments = result["permission_assignments"] )
desired_group_identifiers = result["group_identifiers"] )
desired_user_to_group_identifiers = result["user_to_group_identifiers"]
for group in group_info: permission_configs = None
with open(current_app.config["SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_ABSOLUTE_PATH"]) as file:
permission_configs = yaml.safe_load(file)
group_permissions_by_group: dict[str, GroupPermissionsDict] = {}
if current_app.config["SPIFFWORKFLOW_BACKEND_DEFAULT_USER_GROUP"]:
default_group_identifier = current_app.config["SPIFFWORKFLOW_BACKEND_DEFAULT_USER_GROUP"]
group_permissions_by_group[default_group_identifier] = {
"name": default_group_identifier,
"users": [],
"permissions": [],
}
if "groups" in permission_configs:
for group_identifier, group_config in permission_configs["groups"].items():
group_info: GroupPermissionsDict = {"name": group_identifier, "users": [], "permissions": []}
for username in group_config["users"]:
group_info["users"].append(username)
group_permissions_by_group[group_identifier] = group_info
if "permissions" in permission_configs:
for _permission_identifier, permission_config in permission_configs["permissions"].items():
uri = permission_config["uri"]
for group_identifier in permission_config["groups"]:
group_permissions_by_group[group_identifier]["permissions"].append(
{"actions": permission_config["allowed_permissions"], "uri": uri}
)
return list(group_permissions_by_group.values())
@classmethod
def add_permissions_from_group_permissions(
cls, group_permissions: list[GroupPermissionsDict], user_model: Optional[UserModel] = None
) -> AddedPermissionDict:
unique_user_group_identifiers: Set[str] = set()
user_to_group_identifiers: list[UserToGroupDict] = []
permission_assignments = []
default_group = None
default_group_identifier = current_app.config["SPIFFWORKFLOW_BACKEND_DEFAULT_USER_GROUP"]
if default_group_identifier:
default_group = GroupService.find_or_create_group(default_group_identifier)
unique_user_group_identifiers.add(default_group_identifier)
for group in group_permissions:
group_identifier = group["name"] group_identifier = group["name"]
GroupService.find_or_create_group(group_identifier)
for username in group["users"]: for username in group["users"]:
if user_model and username != user_model.username:
continue
user_to_group_dict: UserToGroupDict = { user_to_group_dict: UserToGroupDict = {
"username": username, "username": username,
"group_identifier": group_identifier, "group_identifier": group_identifier,
} }
desired_user_to_group_identifiers.append(user_to_group_dict) user_to_group_identifiers.append(user_to_group_dict)
GroupService.add_user_to_group_or_add_to_waiting(username, group_identifier) GroupService.add_user_to_group_or_add_to_waiting(username, group_identifier)
desired_group_identifiers.add(group_identifier) unique_user_group_identifiers.add(group_identifier)
for group in group_permissions:
group_identifier = group["name"]
if user_model and group_identifier not in unique_user_group_identifiers:
continue
for permission in group["permissions"]: for permission in group["permissions"]:
for crud_op in permission["actions"]: for crud_op in permission["actions"]:
desired_permission_assignments.extend( permission_assignments.extend(
cls.add_permission_from_uri_or_macro( cls.add_permission_from_uri_or_macro(
group_identifier=group_identifier, group_identifier=group_identifier,
target=permission["uri"], target=permission["uri"],
permission=crud_op, permission=crud_op,
) )
) )
desired_group_identifiers.add(group_identifier) unique_user_group_identifiers.add(group_identifier)
if default_group is not None:
if user_model:
cls.associate_user_with_group(user_model, default_group)
else:
for user in UserModel.query.all():
cls.associate_user_with_group(user, default_group)
return {
"group_identifiers": unique_user_group_identifiers,
"permission_assignments": permission_assignments,
"user_to_group_identifiers": user_to_group_identifiers,
}
@classmethod
def remove_old_permissions_from_added_permissions(
cls,
added_permissions: AddedPermissionDict,
initial_permission_assignments: list[PermissionAssignmentModel],
initial_user_to_group_assignments: list[UserGroupAssignmentModel],
) -> None:
added_permission_assignments = added_permissions["permission_assignments"]
added_group_identifiers = added_permissions["group_identifiers"]
added_user_to_group_identifiers = added_permissions["user_to_group_identifiers"]
for ipa in initial_permission_assignments: for ipa in initial_permission_assignments:
if ipa not in desired_permission_assignments: if ipa not in added_permission_assignments:
db.session.delete(ipa) db.session.delete(ipa)
for iutga in initial_user_to_group_assignments: for iutga in initial_user_to_group_assignments:
@ -743,19 +765,23 @@ class AuthorizationService:
"username": iutga.user.username, "username": iutga.user.username,
"group_identifier": iutga.group.identifier, "group_identifier": iutga.group.identifier,
} }
if current_user_dict not in desired_user_to_group_identifiers: if current_user_dict not in added_user_to_group_identifiers:
db.session.delete(iutga) db.session.delete(iutga)
# do not remove the default user group # do not remove the default user group
desired_group_identifiers.add(current_app.config["SPIFFWORKFLOW_BACKEND_DEFAULT_USER_GROUP"]) added_group_identifiers.add(current_app.config["SPIFFWORKFLOW_BACKEND_DEFAULT_USER_GROUP"])
groups_to_delete = GroupModel.query.filter(GroupModel.identifier.not_in(desired_group_identifiers)).all() groups_to_delete = GroupModel.query.filter(GroupModel.identifier.not_in(added_group_identifiers)).all()
for gtd in groups_to_delete: for gtd in groups_to_delete:
db.session.delete(gtd) db.session.delete(gtd)
db.session.commit() db.session.commit()
@classmethod
class KeycloakAuthorization: def refresh_permissions(cls, group_permissions: list[GroupPermissionsDict]) -> None:
"""Interface with Keycloak server.""" """Adds new permission assignments and deletes old ones."""
initial_permission_assignments = PermissionAssignmentModel.query.all()
initial_user_to_group_assignments = UserGroupAssignmentModel.query.all()
# class KeycloakClient: group_permissions = group_permissions + cls.parse_permissions_yaml_into_group_info()
added_permissions = cls.add_permissions_from_group_permissions(group_permissions)
cls.remove_old_permissions_from_added_permissions(
added_permissions, initial_permission_assignments, initial_user_to_group_assignments
)

View File

@ -1,4 +1,3 @@
"""Group_service."""
from typing import Optional from typing import Optional
from spiffworkflow_backend.models.db import db from spiffworkflow_backend.models.db import db
@ -8,11 +7,8 @@ from spiffworkflow_backend.services.user_service import UserService
class GroupService: class GroupService:
"""GroupService."""
@classmethod @classmethod
def find_or_create_group(cls, group_identifier: str) -> GroupModel: def find_or_create_group(cls, group_identifier: str) -> GroupModel:
"""Find_or_create_group."""
group: Optional[GroupModel] = GroupModel.query.filter_by(identifier=group_identifier).first() group: Optional[GroupModel] = GroupModel.query.filter_by(identifier=group_identifier).first()
if group is None: if group is None:
group = GroupModel(identifier=group_identifier) group = GroupModel(identifier=group_identifier)
@ -23,7 +19,6 @@ class GroupService:
@classmethod @classmethod
def add_user_to_group_or_add_to_waiting(cls, username: str, group_identifier: str) -> None: def add_user_to_group_or_add_to_waiting(cls, username: str, group_identifier: str) -> None:
"""Add_user_to_group_or_add_to_waiting."""
group = cls.find_or_create_group(group_identifier) group = cls.find_or_create_group(group_identifier)
user = UserModel.query.filter_by(username=username).first() user = UserModel.query.filter_by(username=username).first()
if user: if user:

View File

@ -25,7 +25,6 @@ from uuid import UUID
import dateparser import dateparser
import pytz import pytz
from flask import current_app from flask import current_app
from flask import g
from lxml import etree # type: ignore from lxml import etree # type: ignore
from lxml.etree import XMLSyntaxError # type: ignore from lxml.etree import XMLSyntaxError # type: ignore
from RestrictedPython import safe_globals # type: ignore from RestrictedPython import safe_globals # type: ignore
@ -1108,7 +1107,7 @@ class ProcessInstanceProcessor:
# TODO: do_engine_steps without a lock # TODO: do_engine_steps without a lock
self.do_engine_steps(save=True) self.do_engine_steps(save=True)
def manual_complete_task(self, task_id: str, execute: bool) -> None: def manual_complete_task(self, task_id: str, execute: bool, user: UserModel) -> None:
"""Mark the task complete optionally executing it.""" """Mark the task complete optionally executing it."""
spiff_task = self.bpmn_process_instance.get_task_from_id(UUID(task_id)) spiff_task = self.bpmn_process_instance.get_task_from_id(UUID(task_id))
event_type = ProcessInstanceEventType.task_skipped.value event_type = ProcessInstanceEventType.task_skipped.value
@ -1121,7 +1120,7 @@ class ProcessInstanceProcessor:
f" instance {self.process_instance_model.id}" f" instance {self.process_instance_model.id}"
) )
human_task = HumanTaskModel.query.filter_by(task_id=task_id).first() human_task = HumanTaskModel.query.filter_by(task_id=task_id).first()
self.complete_task(spiff_task, human_task=human_task, user=g.user) self.complete_task(spiff_task, human_task=human_task, user=user)
elif execute: elif execute:
current_app.logger.info( current_app.logger.info(
f"Manually executing Task {spiff_task.task_spec.name} of process" f"Manually executing Task {spiff_task.task_spec.name} of process"

View File

@ -11,13 +11,13 @@
<bpmn:scriptTask id="save_key1"> <bpmn:scriptTask id="save_key1">
<bpmn:incoming>Flow_1j4jzft</bpmn:incoming> <bpmn:incoming>Flow_1j4jzft</bpmn:incoming>
<bpmn:outgoing>Flow_10xyk22</bpmn:outgoing> <bpmn:outgoing>Flow_10xyk22</bpmn:outgoing>
<bpmn:script>save_process_instance_metadata({"key1": "value1"})</bpmn:script> <bpmn:script>key1 = "value1"</bpmn:script>
</bpmn:scriptTask> </bpmn:scriptTask>
<bpmn:sequenceFlow id="Flow_10xyk22" sourceRef="save_key1" targetRef="save_key2" /> <bpmn:sequenceFlow id="Flow_10xyk22" sourceRef="save_key1" targetRef="save_key2" />
<bpmn:scriptTask id="save_key2"> <bpmn:scriptTask id="save_key2">
<bpmn:incoming>Flow_10xyk22</bpmn:incoming> <bpmn:incoming>Flow_10xyk22</bpmn:incoming>
<bpmn:outgoing>Flow_01xr2ac</bpmn:outgoing> <bpmn:outgoing>Flow_01xr2ac</bpmn:outgoing>
<bpmn:script>save_process_instance_metadata({"key2": "value2", "key3": "value3"})</bpmn:script> <bpmn:script>key2 = "value2"; key3 = "value3"</bpmn:script>
</bpmn:scriptTask> </bpmn:scriptTask>
<bpmn:sequenceFlow id="Flow_01xr2ac" sourceRef="save_key2" targetRef="Event_1s123jg" /> <bpmn:sequenceFlow id="Flow_01xr2ac" sourceRef="save_key2" targetRef="Event_1s123jg" />
</bpmn:process> </bpmn:process>

View File

@ -14,6 +14,7 @@ from werkzeug.test import TestResponse # type: ignore
from spiffworkflow_backend.exceptions.api_error import ApiError from spiffworkflow_backend.exceptions.api_error import ApiError
from spiffworkflow_backend.models.db import db from spiffworkflow_backend.models.db import db
from spiffworkflow_backend.models.message_instance import MessageInstanceModel
from spiffworkflow_backend.models.permission_assignment import Permission from spiffworkflow_backend.models.permission_assignment import Permission
from spiffworkflow_backend.models.permission_target import PermissionTargetModel from spiffworkflow_backend.models.permission_target import PermissionTargetModel
from spiffworkflow_backend.models.process_group import ProcessGroup from spiffworkflow_backend.models.process_group import ProcessGroup
@ -26,9 +27,11 @@ from spiffworkflow_backend.models.process_model import ProcessModelInfoSchema
from spiffworkflow_backend.models.user import UserModel from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.authorization_service import AuthorizationService from spiffworkflow_backend.services.authorization_service import AuthorizationService
from spiffworkflow_backend.services.file_system_service import FileSystemService from spiffworkflow_backend.services.file_system_service import FileSystemService
from spiffworkflow_backend.services.process_instance_processor import ProcessInstanceProcessor
from spiffworkflow_backend.services.process_instance_queue_service import ( from spiffworkflow_backend.services.process_instance_queue_service import (
ProcessInstanceQueueService, ProcessInstanceQueueService,
) )
from spiffworkflow_backend.services.process_instance_service import ProcessInstanceService
from spiffworkflow_backend.services.process_model_service import ProcessModelService from spiffworkflow_backend.services.process_model_service import ProcessModelService
from spiffworkflow_backend.services.user_service import UserService from spiffworkflow_backend.services.user_service import UserService
@ -56,7 +59,6 @@ class BaseTest:
@staticmethod @staticmethod
def logged_in_headers(user: UserModel, _redirect_url: str = "http://some/frontend/url") -> Dict[str, str]: def logged_in_headers(user: UserModel, _redirect_url: str = "http://some/frontend/url") -> Dict[str, str]:
"""Logged_in_headers."""
return dict(Authorization="Bearer " + user.encode_auth_token()) return dict(Authorization="Bearer " + user.encode_auth_token())
def create_group_and_model_with_bpmn( def create_group_and_model_with_bpmn(
@ -314,7 +316,6 @@ class BaseTest:
target_uri: str = PermissionTargetModel.URI_ALL, target_uri: str = PermissionTargetModel.URI_ALL,
permission_names: Optional[list[str]] = None, permission_names: Optional[list[str]] = None,
) -> UserModel: ) -> UserModel:
"""Create_user_with_permission."""
user = BaseTest.find_or_create_user(username=username) user = BaseTest.find_or_create_user(username=username)
return cls.add_permissions_to_user(user, target_uri=target_uri, permission_names=permission_names) return cls.add_permissions_to_user(user, target_uri=target_uri, permission_names=permission_names)
@ -325,7 +326,6 @@ class BaseTest:
target_uri: str = PermissionTargetModel.URI_ALL, target_uri: str = PermissionTargetModel.URI_ALL,
permission_names: Optional[list[str]] = None, permission_names: Optional[list[str]] = None,
) -> UserModel: ) -> UserModel:
"""Add_permissions_to_user."""
permission_target = AuthorizationService.find_or_create_permission_target(target_uri) permission_target = AuthorizationService.find_or_create_permission_target(target_uri)
if permission_names is None: if permission_names is None:
@ -401,3 +401,65 @@ class BaseTest:
def empty_report_metadata_body(self) -> ReportMetadata: def empty_report_metadata_body(self) -> ReportMetadata:
return {"filter_by": [], "columns": [], "order_by": []} return {"filter_by": [], "columns": [], "order_by": []}
def start_sender_process(
self,
client: FlaskClient,
payload: dict,
group_name: str = "test_group",
) -> ProcessInstanceModel:
process_model = load_test_spec(
"test_group/message",
process_model_source_directory="message_send_one_conversation",
bpmn_file_name="message_sender.bpmn", # Slightly misnamed, it sends and receives
)
process_instance = self.create_process_instance_from_process_model(process_model)
processor_send_receive = ProcessInstanceProcessor(process_instance)
processor_send_receive.do_engine_steps(save=True)
task = processor_send_receive.get_all_user_tasks()[0]
human_task = process_instance.active_human_tasks[0]
ProcessInstanceService.complete_form_task(
processor_send_receive,
task,
payload,
process_instance.process_initiator,
human_task,
)
processor_send_receive.save()
return process_instance
def assure_a_message_was_sent(self, process_instance: ProcessInstanceModel, payload: dict) -> None:
# There should be one new send message for the given process instance.
send_messages = (
MessageInstanceModel.query.filter_by(message_type="send")
.filter_by(process_instance_id=process_instance.id)
.order_by(MessageInstanceModel.id)
.all()
)
assert len(send_messages) == 1
send_message = send_messages[0]
assert send_message.payload == payload, "The send message should match up with the payload"
assert send_message.name == "Request Approval"
assert send_message.status == "ready"
def assure_there_is_a_process_waiting_on_a_message(self, process_instance: ProcessInstanceModel) -> None:
# There should be one new send message for the given process instance.
waiting_messages = (
MessageInstanceModel.query.filter_by(message_type="receive")
.filter_by(status="ready")
.filter_by(process_instance_id=process_instance.id)
.order_by(MessageInstanceModel.id)
.all()
)
assert len(waiting_messages) == 1
waiting_message = waiting_messages[0]
self.assure_correlation_properties_are_right(waiting_message)
def assure_correlation_properties_are_right(self, message: MessageInstanceModel) -> None:
# Correlation Properties should match up
po_curr = next(c for c in message.correlation_rules if c.name == "po_number")
customer_curr = next(c for c in message.correlation_rules if c.name == "customer_id")
assert po_curr is not None
assert customer_curr is not None

View File

@ -10,7 +10,7 @@ class TestDebugController(BaseTest):
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
) -> None: ) -> None:
response = client.get( response = client.post(
"/v1.0/debug/test-raise-error", "/v1.0/debug/test-raise-error",
) )
assert response.status_code == 500 assert response.status_code == 500

View File

@ -25,14 +25,12 @@ class TestLoggingService(BaseTest):
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel, with_super_admin_user: UserModel,
) -> None: ) -> None:
self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group")
initiator_user = self.find_or_create_user("initiator_user") initiator_user = self.find_or_create_user("initiator_user")
assert initiator_user.principal is not None assert initiator_user.principal is not None
AuthorizationService.import_permissions_from_yaml_file() AuthorizationService.import_permissions_from_yaml_file()
process_model = load_test_spec( process_model = load_test_spec(
process_model_id="misc/category_number_one/simple_form", process_model_id="misc/category_number_one/simple_form",
# bpmn_file_name="simp.bpmn",
process_model_source_directory="simple_form", process_model_source_directory="simple_form",
) )
process_instance = self.create_process_instance_from_process_model( process_instance = self.create_process_instance_from_process_model(
@ -85,14 +83,12 @@ class TestLoggingService(BaseTest):
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel, with_super_admin_user: UserModel,
) -> None: ) -> None:
self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group")
initiator_user = self.find_or_create_user("initiator_user") initiator_user = self.find_or_create_user("initiator_user")
assert initiator_user.principal is not None assert initiator_user.principal is not None
AuthorizationService.import_permissions_from_yaml_file() AuthorizationService.import_permissions_from_yaml_file()
process_model = load_test_spec( process_model = load_test_spec(
process_model_id="misc/category_number_one/simple_form", process_model_id="misc/category_number_one/simple_form",
# bpmn_file_name="simp.bpmn",
process_model_source_directory="simple_form", process_model_source_directory="simple_form",
) )
process_instance = self.create_process_instance_from_process_model( process_instance = self.create_process_instance_from_process_model(

View File

@ -0,0 +1,63 @@
import pytest
from flask import Flask
from flask import g
from flask.testing import FlaskClient
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from spiffworkflow_backend.exceptions.api_error import ApiError
from spiffworkflow_backend.models.message_instance import MessageInstanceModel
from spiffworkflow_backend.routes.messages_controller import message_send
class TestMessages(BaseTest):
def test_message_from_api_into_running_process(
self,
app: Flask,
client: FlaskClient,
with_db_and_bpmn_file_cleanup: None,
) -> None:
"""Test sending a message to a running process via the API.
This example workflow will send a message called 'request_approval' and then wait for a response message
of 'approval_result'. This test assures that it will fire the message with the correct correlation properties
and will respond only to a message called 'approval_result' that has the matching correlation properties,
as sent by an API Call.
"""
payload = {
"customer_id": "Sartography",
"po_number": 1001,
"description": "We built a new feature for messages!",
"amount": "100.00",
}
process_instance = self.start_sender_process(client, payload, "test_from_api")
self.assure_a_message_was_sent(process_instance, payload)
self.assure_there_is_a_process_waiting_on_a_message(process_instance)
g.user = process_instance.process_initiator
# Make an API call to the service endpoint, but use the wrong po number
with pytest.raises(ApiError):
message_send("Approval Result", {"payload": {"po_number": 5001}})
# Should return an error when making an API call for right po number, wrong client
with pytest.raises(ApiError):
message_send(
"Approval Result",
{"payload": {"po_number": 1001, "customer_id": "jon"}},
)
# No error when calling with the correct parameters
message_send(
"Approval Result",
{"payload": {"po_number": 1001, "customer_id": "Sartography"}},
)
# There is no longer a waiting message
waiting_messages = (
MessageInstanceModel.query.filter_by(message_type="receive")
.filter_by(status="ready")
.filter_by(process_instance_id=process_instance.id)
.all()
)
assert len(waiting_messages) == 0
# The process has completed
assert process_instance.status == "complete"

View File

@ -182,7 +182,7 @@ class TestProcessApi(BaseTest):
user=with_super_admin_user, user=with_super_admin_user,
) )
response = client.post( response = client.post(
f"/v1.0/process-models-natural-language/{process_group_id}", f"/v1.0/process-model-natural-language/{process_group_id}",
content_type="application/json", content_type="application/json",
data=json.dumps(body), data=json.dumps(body),
headers=self.logged_in_headers(with_super_admin_user), headers=self.logged_in_headers(with_super_admin_user),
@ -238,9 +238,6 @@ class TestProcessApi(BaseTest):
process_model_identifier = f"{process_group_id}/{process_model_id}" process_model_identifier = f"{process_group_id}/{process_model_id}"
initial_primary_process_id = "sample" initial_primary_process_id = "sample"
terminal_primary_process_id = "new_process_id" terminal_primary_process_id = "new_process_id"
self.create_process_group_with_api(
client=client, user=with_super_admin_user, process_group_id=process_group_id
)
bpmn_file_name = f"{process_model_id}.bpmn" bpmn_file_name = f"{process_model_id}.bpmn"
bpmn_file_source_directory = process_model_id bpmn_file_source_directory = process_model_id
@ -282,14 +279,11 @@ class TestProcessApi(BaseTest):
) -> None: ) -> None:
"""Test_process_model_delete.""" """Test_process_model_delete."""
process_group_id = "test_process_group" process_group_id = "test_process_group"
process_group_description = "Test Process Group"
process_model_id = "sample" process_model_id = "sample"
process_model_identifier = f"{process_group_id}/{process_model_id}" process_model_identifier = f"{process_group_id}/{process_model_id}"
self.create_process_group_with_api(client, with_super_admin_user, process_group_id, process_group_description) process_model = load_test_spec(
self.create_process_model_with_api(
client,
process_model_id=process_model_identifier, process_model_id=process_model_identifier,
user=with_super_admin_user, process_model_source_directory=process_model_id,
) )
# assert we have a model # assert we have a model
@ -2349,7 +2343,6 @@ class TestProcessApi(BaseTest):
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel, with_super_admin_user: UserModel,
) -> None: ) -> None:
"""Test_correct_user_can_get_and_update_a_task."""
initiator_user = self.find_or_create_user("testuser4") initiator_user = self.find_or_create_user("testuser4")
finance_user = self.find_or_create_user("testuser2") finance_user = self.find_or_create_user("testuser2")
assert initiator_user.principal is not None assert initiator_user.principal is not None
@ -2372,15 +2365,8 @@ class TestProcessApi(BaseTest):
bpmn_file_location=bpmn_file_location, bpmn_file_location=bpmn_file_location,
) )
# process_model = load_test_spec(
# process_model_id="model_with_lanes",
# bpmn_file_name="lanes.bpmn",
# process_group_id="finance",
# )
response = self.create_process_instance_from_process_model_id_with_api( response = self.create_process_instance_from_process_model_id_with_api(
client, client,
# process_model.process_group_id,
process_model_identifier, process_model_identifier,
headers=self.logged_in_headers(initiator_user), headers=self.logged_in_headers(initiator_user),
) )
@ -3041,7 +3027,7 @@ class TestProcessApi(BaseTest):
# #
# # modified_process_model_id = process_model_identifier.replace("/", ":") # # modified_process_model_id = process_model_identifier.replace("/", ":")
# # response = client.post( # # response = client.post(
# # f"/v1.0/process-models/{modified_process_model_id}/publish?branch_to_update=staging", # # f"/v1.0/process-model-publish/{modified_process_model_id}?branch_to_update=staging",
# # headers=self.logged_in_headers(with_super_admin_user), # # headers=self.logged_in_headers(with_super_admin_user),
# # ) # # )
# #
@ -3059,6 +3045,17 @@ class TestProcessApi(BaseTest):
bpmn_file_name="save_process_instance_metadata.bpmn", bpmn_file_name="save_process_instance_metadata.bpmn",
process_model_source_directory="save_process_instance_metadata", process_model_source_directory="save_process_instance_metadata",
) )
ProcessModelService.update_process_model(
process_model,
{
"metadata_extraction_paths": [
{"key": "key1", "path": "key1"},
{"key": "key2", "path": "key2"},
{"key": "key3", "path": "key3"},
]
},
)
process_instance = self.create_process_instance_from_process_model( process_instance = self.create_process_instance_from_process_model(
process_model=process_model, user=with_super_admin_user process_model=process_model, user=with_super_admin_user
) )
@ -3196,6 +3193,16 @@ class TestProcessApi(BaseTest):
bpmn_file_name="save_process_instance_metadata.bpmn", bpmn_file_name="save_process_instance_metadata.bpmn",
process_model_source_directory="save_process_instance_metadata", process_model_source_directory="save_process_instance_metadata",
) )
ProcessModelService.update_process_model(
process_model,
{
"metadata_extraction_paths": [
{"key": "key1", "path": "key1"},
{"key": "key2", "path": "key2"},
{"key": "key3", "path": "key3"},
]
},
)
process_instance = self.create_process_instance_from_process_model( process_instance = self.create_process_instance_from_process_model(
process_model=process_model, user=with_super_admin_user process_model=process_model, user=with_super_admin_user
) )
@ -3270,7 +3277,6 @@ class TestProcessApi(BaseTest):
with_super_admin_user: UserModel, with_super_admin_user: UserModel,
) -> None: ) -> None:
"""Test_process_instance_list_can_order_by_metadata.""" """Test_process_instance_list_can_order_by_metadata."""
self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group")
process_model = load_test_spec( process_model = load_test_spec(
"test_group/hello_world", "test_group/hello_world",
process_model_source_directory="nested-task-data-structure", process_model_source_directory="nested-task-data-structure",

View File

@ -1,31 +1,21 @@
"""Test_get_localtime."""
from operator import itemgetter from operator import itemgetter
from flask.app import Flask from flask.app import Flask
from flask.testing import FlaskClient
from tests.spiffworkflow_backend.helpers.base_test import BaseTest from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from spiffworkflow_backend.models.script_attributes_context import ( from spiffworkflow_backend.models.script_attributes_context import (
ScriptAttributesContext, ScriptAttributesContext,
) )
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.scripts.get_all_permissions import GetAllPermissions from spiffworkflow_backend.scripts.get_all_permissions import GetAllPermissions
from spiffworkflow_backend.services.authorization_service import AuthorizationService from spiffworkflow_backend.services.authorization_service import AuthorizationService
class TestGetAllPermissions(BaseTest): class TestGetAllPermissions(BaseTest):
"""TestGetAllPermissions."""
def test_can_get_all_permissions( def test_can_get_all_permissions(
self, self,
app: Flask, app: Flask,
client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
"""Test_can_get_all_permissions."""
self.find_or_create_user("test_user")
# now that we have everything, try to clear it out... # now that we have everything, try to clear it out...
script_attributes_context = ScriptAttributesContext( script_attributes_context = ScriptAttributesContext(
task=None, task=None,

View File

@ -3,14 +3,12 @@ import json
from flask import g from flask import g
from flask.app import Flask from flask.app import Flask
from flask.testing import FlaskClient
from tests.spiffworkflow_backend.helpers.base_test import BaseTest from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from spiffworkflow_backend.models.db import db from spiffworkflow_backend.models.db import db
from spiffworkflow_backend.models.script_attributes_context import ( from spiffworkflow_backend.models.script_attributes_context import (
ScriptAttributesContext, ScriptAttributesContext,
) )
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.scripts.get_current_user import GetCurrentUser from spiffworkflow_backend.scripts.get_current_user import GetCurrentUser
@ -18,11 +16,8 @@ class TestGetCurrentUser(BaseTest):
def test_get_current_user( def test_get_current_user(
self, self,
app: Flask, app: Flask,
client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
"""Test_can_get_members_of_a_group."""
testuser1 = self.find_or_create_user("testuser1") testuser1 = self.find_or_create_user("testuser1")
testuser1.tenant_specific_field_1 = "456" testuser1.tenant_specific_field_1 = "456"
db.session.add(testuser1) db.session.add(testuser1)

View File

@ -1,12 +1,9 @@
"""Test_get_localtime."""
from flask.app import Flask from flask.app import Flask
from flask.testing import FlaskClient
from tests.spiffworkflow_backend.helpers.base_test import BaseTest from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
from spiffworkflow_backend.models.db import db from spiffworkflow_backend.models.db import db
from spiffworkflow_backend.models.group import GroupModel from spiffworkflow_backend.models.group import GroupModel
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.process_instance_processor import ( from spiffworkflow_backend.services.process_instance_processor import (
ProcessInstanceProcessor, ProcessInstanceProcessor,
) )
@ -14,16 +11,11 @@ from spiffworkflow_backend.services.user_service import UserService
class TestGetGroupMembers(BaseTest): class TestGetGroupMembers(BaseTest):
"""TestGetGroupMembers."""
def test_can_get_members_of_a_group( def test_can_get_members_of_a_group(
self, self,
app: Flask, app: Flask,
client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
"""Test_can_get_members_of_a_group."""
initiator_user = self.find_or_create_user("initiator_user") initiator_user = self.find_or_create_user("initiator_user")
testuser1 = self.find_or_create_user("testuser1") testuser1 = self.find_or_create_user("testuser1")
testuser2 = self.find_or_create_user("testuser2") testuser2 = self.find_or_create_user("testuser2")
@ -38,7 +30,6 @@ class TestGetGroupMembers(BaseTest):
UserService.add_user_to_group(testuser2, group_a) UserService.add_user_to_group(testuser2, group_a)
UserService.add_user_to_group(testuser3, group_b) UserService.add_user_to_group(testuser3, group_b)
self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group")
process_model = load_test_spec( process_model = load_test_spec(
process_model_id="test_group/get_group_members", process_model_id="test_group/get_group_members",
bpmn_file_name="get_group_members.bpmn", bpmn_file_name="get_group_members.bpmn",

View File

@ -1,10 +1,7 @@
"""Test_get_localtime."""
from flask.app import Flask from flask.app import Flask
from flask.testing import FlaskClient
from tests.spiffworkflow_backend.helpers.base_test import BaseTest from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.authorization_service import AuthorizationService from spiffworkflow_backend.services.authorization_service import AuthorizationService
from spiffworkflow_backend.services.process_instance_processor import ( from spiffworkflow_backend.services.process_instance_processor import (
ProcessInstanceProcessor, ProcessInstanceProcessor,
@ -18,12 +15,8 @@ class TestGetLastUserCompletingTask(BaseTest):
def test_get_last_user_completing_task_script_works( def test_get_last_user_completing_task_script_works(
self, self,
app: Flask, app: Flask,
client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
"""Test_sets_permission_correctly_on_human_task."""
self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group")
initiator_user = self.find_or_create_user("initiator_user") initiator_user = self.find_or_create_user("initiator_user")
assert initiator_user.principal is not None assert initiator_user.principal is not None
AuthorizationService.import_permissions_from_yaml_file() AuthorizationService.import_permissions_from_yaml_file()

View File

@ -1,9 +1,7 @@
"""Test_get_localtime."""
import datetime import datetime
import pytz import pytz
from flask.app import Flask from flask.app import Flask
from flask.testing import FlaskClient
from tests.spiffworkflow_backend.helpers.base_test import BaseTest from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
@ -20,10 +18,7 @@ from spiffworkflow_backend.services.process_instance_service import (
class TestGetLocaltime(BaseTest): class TestGetLocaltime(BaseTest):
"""TestProcessAPi."""
def test_get_localtime_script_directly(self) -> None: def test_get_localtime_script_directly(self) -> None:
"""Test_get_localtime_script_directly."""
current_time = datetime.datetime.now() current_time = datetime.datetime.now()
timezone = "US/Pacific" timezone = "US/Pacific"
process_model_identifier = "test_process_model" process_model_identifier = "test_process_model"
@ -44,17 +39,14 @@ class TestGetLocaltime(BaseTest):
def test_get_localtime_script_through_bpmn( def test_get_localtime_script_through_bpmn(
self, self,
app: Flask, app: Flask,
client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
) -> None: ) -> None:
"""Test_process_instance_run."""
initiator_user = self.find_or_create_user("initiator_user") initiator_user = self.find_or_create_user("initiator_user")
self.add_permissions_to_user( self.add_permissions_to_user(
initiator_user, initiator_user,
target_uri="/v1.0/process-groups", target_uri="/v1.0/process-groups",
permission_names=["read", "create"], permission_names=["read", "create"],
) )
self.create_process_group_with_api(client=client, user=initiator_user, process_group_id="test_group")
process_model = load_test_spec( process_model = load_test_spec(
process_model_id="test_group/get_localtime", process_model_id="test_group/get_localtime",
bpmn_file_name="get_localtime.bpmn", bpmn_file_name="get_localtime.bpmn",

View File

@ -1,10 +1,8 @@
"""Test_get_localtime.""" """Test_get_localtime."""
from flask.app import Flask from flask.app import Flask
from flask.testing import FlaskClient
from tests.spiffworkflow_backend.helpers.base_test import BaseTest from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.authorization_service import AuthorizationService from spiffworkflow_backend.services.authorization_service import AuthorizationService
from spiffworkflow_backend.services.process_instance_processor import ( from spiffworkflow_backend.services.process_instance_processor import (
ProcessInstanceProcessor, ProcessInstanceProcessor,
@ -18,19 +16,15 @@ class TestGetProcessInitiatorUser(BaseTest):
def test_get_process_initiator_user( def test_get_process_initiator_user(
self, self,
app: Flask, app: Flask,
client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
"""Test_sets_permission_correctly_on_human_task.""" """Test_sets_permission_correctly_on_human_task."""
self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group")
initiator_user = self.find_or_create_user("initiator_user") initiator_user = self.find_or_create_user("initiator_user")
assert initiator_user.principal is not None assert initiator_user.principal is not None
AuthorizationService.import_permissions_from_yaml_file() AuthorizationService.import_permissions_from_yaml_file()
process_model = load_test_spec( process_model = load_test_spec(
process_model_id="misc/category_number_one/simple_form", process_model_id="misc/category_number_one/simple_form",
# bpmn_file_name="simp.bpmn",
process_model_source_directory="simple_form", process_model_source_directory="simple_form",
) )
process_instance = self.create_process_instance_from_process_model( process_instance = self.create_process_instance_from_process_model(

View File

@ -1,7 +1,6 @@
"""Test_get_localtime.""" """Test_get_localtime."""
import pytest import pytest
from flask.app import Flask from flask.app import Flask
from flask.testing import FlaskClient
from tests.spiffworkflow_backend.helpers.base_test import BaseTest from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
@ -15,7 +14,6 @@ class TestRefreshPermissions(BaseTest):
def test_refresh_permissions_requires_elevated_permission( def test_refresh_permissions_requires_elevated_permission(
self, self,
app: Flask, app: Flask,
client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
) -> None: ) -> None:
basic_user = self.find_or_create_user("basic_user") basic_user = self.find_or_create_user("basic_user")

View File

@ -1,42 +0,0 @@
"""Test_get_localtime."""
from flask.app import Flask
from flask.testing import FlaskClient
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
from spiffworkflow_backend.models.process_instance_metadata import (
ProcessInstanceMetadataModel,
)
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.process_instance_processor import (
ProcessInstanceProcessor,
)
class TestSaveProcessInstanceMetadata(BaseTest):
"""TestSaveProcessInstanceMetadata."""
def test_can_save_process_instance_metadata(
self,
app: Flask,
client: FlaskClient,
with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None:
"""Test_can_save_process_instance_metadata."""
self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group")
process_model = load_test_spec(
process_model_id="save_process_instance_metadata/save_process_instance_metadata",
bpmn_file_name="save_process_instance_metadata.bpmn",
process_model_source_directory="save_process_instance_metadata",
)
process_instance = self.create_process_instance_from_process_model(
process_model=process_model, user=with_super_admin_user
)
processor = ProcessInstanceProcessor(process_instance)
processor.do_engine_steps(save=True)
process_instance_metadata = ProcessInstanceMetadataModel.query.filter_by(
process_instance_id=process_instance.id
).all()
assert len(process_instance_metadata) == 3

View File

@ -3,11 +3,11 @@ import pytest
from flask import Flask from flask import Flask
from flask.testing import FlaskClient from flask.testing import FlaskClient
from tests.spiffworkflow_backend.helpers.base_test import BaseTest from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
from spiffworkflow_backend.models.group import GroupModel from spiffworkflow_backend.models.group import GroupModel
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.models.user import UserNotFoundError
from spiffworkflow_backend.services.authorization_service import AuthorizationService from spiffworkflow_backend.services.authorization_service import AuthorizationService
from spiffworkflow_backend.services.authorization_service import GroupPermissionsDict
from spiffworkflow_backend.services.authorization_service import InvalidPermissionError from spiffworkflow_backend.services.authorization_service import InvalidPermissionError
from spiffworkflow_backend.services.group_service import GroupService from spiffworkflow_backend.services.group_service import GroupService
from spiffworkflow_backend.services.process_instance_processor import ( from spiffworkflow_backend.services.process_instance_processor import (
@ -16,24 +16,14 @@ from spiffworkflow_backend.services.process_instance_processor import (
from spiffworkflow_backend.services.process_instance_service import ( from spiffworkflow_backend.services.process_instance_service import (
ProcessInstanceService, ProcessInstanceService,
) )
from spiffworkflow_backend.services.process_model_service import ProcessModelService
from spiffworkflow_backend.services.user_service import UserService from spiffworkflow_backend.services.user_service import UserService
class TestAuthorizationService(BaseTest): class TestAuthorizationService(BaseTest):
"""TestAuthorizationService."""
def test_can_raise_if_missing_user(self, app: Flask, with_db_and_bpmn_file_cleanup: None) -> None:
"""Test_can_raise_if_missing_user."""
with pytest.raises(UserNotFoundError):
AuthorizationService.import_permissions_from_yaml_file(raise_if_missing_user=True)
def test_does_not_fail_if_user_not_created(self, app: Flask, with_db_and_bpmn_file_cleanup: None) -> None: def test_does_not_fail_if_user_not_created(self, app: Flask, with_db_and_bpmn_file_cleanup: None) -> None:
"""Test_does_not_fail_if_user_not_created."""
AuthorizationService.import_permissions_from_yaml_file() AuthorizationService.import_permissions_from_yaml_file()
def test_can_import_permissions_from_yaml(self, app: Flask, with_db_and_bpmn_file_cleanup: None) -> None: def test_can_import_permissions_from_yaml(self, app: Flask, with_db_and_bpmn_file_cleanup: None) -> None:
"""Test_can_import_permissions_from_yaml."""
usernames = [ usernames = [
"testadmin1", "testadmin1",
"testadmin2", "testadmin2",
@ -56,22 +46,19 @@ class TestAuthorizationService(BaseTest):
assert testuser1_group_identifiers == ["Finance Team", "everybody"] assert testuser1_group_identifiers == ["Finance Team", "everybody"]
assert len(users["testuser2"].groups) == 3 assert len(users["testuser2"].groups) == 3
self.assert_user_has_permission(users["testuser1"], "update", "/v1.0/process-groups/finance/model1") self.assert_user_has_permission(users["testuser1"], "update", "/v1.0/process-groups/finance:model1")
self.assert_user_has_permission(users["testuser1"], "update", "/v1.0/process-groups/finance/") self.assert_user_has_permission(users["testuser1"], "update", "/v1.0/process-groups/finance")
self.assert_user_has_permission(users["testuser1"], "update", "/v1.0/process-groups/", expected_result=False) self.assert_user_has_permission(users["testuser1"], "update", "/v1.0/process-groups/", expected_result=False)
self.assert_user_has_permission(users["testuser4"], "update", "/v1.0/process-groups/finance/model1") self.assert_user_has_permission(users["testuser4"], "read", "/v1.0/process-groups/finance:model1")
# via the user, not the group self.assert_user_has_permission(users["testuser2"], "update", "/v1.0/process-groups/finance:model1")
self.assert_user_has_permission(users["testuser4"], "read", "/v1.0/process-groups/finance/model1") self.assert_user_has_permission(users["testuser2"], "update", "/v1.0/process-groups", expected_result=False)
self.assert_user_has_permission(users["testuser2"], "update", "/v1.0/process-groups/finance/model1") self.assert_user_has_permission(users["testuser2"], "read", "/v1.0/process-groups")
self.assert_user_has_permission(users["testuser2"], "update", "/v1.0/process-groups/", expected_result=False)
self.assert_user_has_permission(users["testuser2"], "read", "/v1.0/process-groups/")
def test_user_can_be_added_to_human_task_on_first_login( def test_user_can_be_added_to_human_task_on_first_login(
self, self,
app: Flask, app: Flask,
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
"""Test_user_can_be_added_to_human_task_on_first_login.""" """Test_user_can_be_added_to_human_task_on_first_login."""
initiator_user = self.find_or_create_user("initiator_user") initiator_user = self.find_or_create_user("initiator_user")
@ -80,16 +67,12 @@ class TestAuthorizationService(BaseTest):
self.find_or_create_user("testuser1") self.find_or_create_user("testuser1")
AuthorizationService.import_permissions_from_yaml_file() AuthorizationService.import_permissions_from_yaml_file()
process_model_identifier = self.create_group_and_model_with_bpmn( process_model = load_test_spec(
client=client, process_model_id="test_group/model_with_lanes",
user=with_super_admin_user,
process_group_id="test_group",
process_model_id="model_with_lanes",
bpmn_file_name="lanes.bpmn", bpmn_file_name="lanes.bpmn",
bpmn_file_location="model_with_lanes", process_model_source_directory="model_with_lanes",
) )
process_model = ProcessModelService.get_process_model(process_model_id=process_model_identifier)
process_instance = self.create_process_instance_from_process_model( process_instance = self.create_process_instance_from_process_model(
process_model=process_model, user=initiator_user process_model=process_model, user=initiator_user
) )
@ -121,7 +104,6 @@ class TestAuthorizationService(BaseTest):
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
) -> None: ) -> None:
"""Test_explode_permissions_all_on_process_group."""
expected_permissions = sorted( expected_permissions = sorted(
[ [
("/event-error-details/some-process-group:some-process-model:*", "read"), ("/event-error-details/some-process-group:some-process-model:*", "read"),
@ -153,6 +135,8 @@ class TestAuthorizationService(BaseTest):
"delete", "delete",
), ),
("/process-instances/some-process-group:some-process-model:*", "read"), ("/process-instances/some-process-group:some-process-model:*", "read"),
("/process-model-natural-language/some-process-group:some-process-model:*", "create"),
("/process-model-publish/some-process-group:some-process-model:*", "create"),
("/process-models/some-process-group:some-process-model:*", "create"), ("/process-models/some-process-group:some-process-model:*", "create"),
("/process-models/some-process-group:some-process-model:*", "delete"), ("/process-models/some-process-group:some-process-model:*", "delete"),
("/process-models/some-process-group:some-process-model:*", "read"), ("/process-models/some-process-group:some-process-model:*", "read"),
@ -174,26 +158,28 @@ class TestAuthorizationService(BaseTest):
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
) -> None: ) -> None:
"""Test_explode_permissions_start_on_process_group.""" """Test_explode_permissions_start_on_process_group."""
expected_permissions = [ expected_permissions = sorted(
("/event-error-details/some-process-group:some-process-model:*", "read"), [
( ("/event-error-details/some-process-group:some-process-model:*", "read"),
"/logs/some-process-group:some-process-model:*", (
"read", "/logs/some-process-group:some-process-model:*",
), "read",
( ),
"/logs/typeahead-filter-values/some-process-group:some-process-model:*", (
"read", "/logs/typeahead-filter-values/some-process-group:some-process-model:*",
), "read",
( ),
"/process-data-file-download/some-process-group:some-process-model:*", (
"read", "/process-data-file-download/some-process-group:some-process-model:*",
), "read",
( ),
"/process-instances/for-me/some-process-group:some-process-model:*", (
"read", "/process-instances/for-me/some-process-group:some-process-model:*",
), "read",
("/process-instances/some-process-group:some-process-model:*", "create"), ),
] ("/process-instances/some-process-group:some-process-model:*", "create"),
]
)
permissions_to_assign = AuthorizationService.explode_permissions( permissions_to_assign = AuthorizationService.explode_permissions(
"start", "PG:/some-process-group/some-process-model" "start", "PG:/some-process-group/some-process-model"
) )
@ -206,7 +192,6 @@ class TestAuthorizationService(BaseTest):
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
) -> None: ) -> None:
"""Test_explode_permissions_all_on_process_model."""
expected_permissions = sorted( expected_permissions = sorted(
[ [
("/event-error-details/some-process-group:some-process-model/*", "read"), ("/event-error-details/some-process-group:some-process-model/*", "read"),
@ -234,6 +219,8 @@ class TestAuthorizationService(BaseTest):
"delete", "delete",
), ),
("/process-instances/some-process-group:some-process-model/*", "read"), ("/process-instances/some-process-group:some-process-model/*", "read"),
("/process-model-natural-language/some-process-group:some-process-model/*", "create"),
("/process-model-publish/some-process-group:some-process-model/*", "create"),
("/process-models/some-process-group:some-process-model/*", "create"), ("/process-models/some-process-group:some-process-model/*", "create"),
("/process-models/some-process-group:some-process-model/*", "delete"), ("/process-models/some-process-group:some-process-model/*", "delete"),
("/process-models/some-process-group:some-process-model/*", "read"), ("/process-models/some-process-group:some-process-model/*", "read"),
@ -255,26 +242,28 @@ class TestAuthorizationService(BaseTest):
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
) -> None: ) -> None:
"""Test_explode_permissions_start_on_process_model.""" """Test_explode_permissions_start_on_process_model."""
expected_permissions = [ expected_permissions = sorted(
( [
"/event-error-details/some-process-group:some-process-model/*", (
"read", "/event-error-details/some-process-group:some-process-model/*",
), "read",
( ),
"/logs/some-process-group:some-process-model/*", (
"read", "/logs/some-process-group:some-process-model/*",
), "read",
("/logs/typeahead-filter-values/some-process-group:some-process-model/*", "read"), ),
( ("/logs/typeahead-filter-values/some-process-group:some-process-model/*", "read"),
"/process-data-file-download/some-process-group:some-process-model/*", (
"read", "/process-data-file-download/some-process-group:some-process-model/*",
), "read",
( ),
"/process-instances/for-me/some-process-group:some-process-model/*", (
"read", "/process-instances/for-me/some-process-group:some-process-model/*",
), "read",
("/process-instances/some-process-group:some-process-model/*", "create"), ),
] ("/process-instances/some-process-group:some-process-model/*", "create"),
]
)
permissions_to_assign = AuthorizationService.explode_permissions( permissions_to_assign = AuthorizationService.explode_permissions(
"start", "PM:/some-process-group/some-process-model" "start", "PM:/some-process-group/some-process-model"
) )
@ -287,32 +276,70 @@ class TestAuthorizationService(BaseTest):
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
) -> None: ) -> None:
expected_permissions = [ expected_permissions = sorted(
("/active-users/*", "read"), [
("/debug/version-info", "read"), ("/active-users/*", "create"),
("/process-groups", "read"), ("/connector-proxy/typeahead/*", "read"),
("/process-instances/find-by-id/*", "read"), ("/debug/version-info", "read"),
("/process-instances/for-me", "create"), ("/process-groups", "read"),
("/process-instances/report-metadata", "read"), ("/process-instances/find-by-id/*", "read"),
("/process-instances/reports/*", "create"), ("/process-instances/for-me", "create"),
("/process-instances/reports/*", "delete"), ("/process-instances/report-metadata", "read"),
("/process-instances/reports/*", "read"), ("/process-instances/reports/*", "create"),
("/process-instances/reports/*", "update"), ("/process-instances/reports/*", "delete"),
("/process-models", "read"), ("/process-instances/reports/*", "read"),
("/processes", "read"), ("/process-instances/reports/*", "update"),
("/processes/callers", "read"), ("/process-models", "read"),
("/service-tasks", "read"), ("/processes", "read"),
("/tasks/*", "create"), ("/processes/callers", "read"),
("/tasks/*", "delete"), ("/service-tasks", "read"),
("/tasks/*", "read"), ("/tasks/*", "create"),
("/tasks/*", "update"), ("/tasks/*", "delete"),
("/user-groups/for-current-user", "read"), ("/tasks/*", "read"),
("/users/exists/by-username", "create"), ("/tasks/*", "update"),
] ("/user-groups/for-current-user", "read"),
("/users/exists/by-username", "create"),
("/users/search", "read"),
]
)
permissions_to_assign = AuthorizationService.explode_permissions("all", "BASIC") permissions_to_assign = AuthorizationService.explode_permissions("all", "BASIC")
permissions_to_assign_tuples = sorted([(p.target_uri, p.permission) for p in permissions_to_assign]) permissions_to_assign_tuples = sorted([(p.target_uri, p.permission) for p in permissions_to_assign])
assert permissions_to_assign_tuples == expected_permissions assert permissions_to_assign_tuples == expected_permissions
def test_explode_permissions_elevated(
self,
app: Flask,
client: FlaskClient,
with_db_and_bpmn_file_cleanup: None,
) -> None:
expected_permissions = sorted(
[
("/authentications", "read"),
("/can-run-privileged-script/*", "create"),
("/debug/*", "create"),
("/messages", "read"),
("/messages/*", "create"),
("/process-instance-reset/*", "create"),
("/process-instance-resume/*", "create"),
("/process-instance-suspend/*", "create"),
("/process-instance-terminate/*", "create"),
("/process-instances/*", "create"),
("/process-instances/*", "delete"),
("/process-instances/*", "read"),
("/process-instances/*", "update"),
("/secrets/*", "create"),
("/secrets/*", "delete"),
("/secrets/*", "read"),
("/secrets/*", "update"),
("/send-event/*", "create"),
("/task-complete/*", "create"),
("/task-data/*", "update"),
]
)
permissions_to_assign = AuthorizationService.explode_permissions("all", "ELEVATED")
permissions_to_assign_tuples = sorted([(p.target_uri, p.permission) for p in permissions_to_assign])
assert permissions_to_assign_tuples == expected_permissions
def test_explode_permissions_all( def test_explode_permissions_all(
self, self,
app: Flask, app: Flask,
@ -387,7 +414,6 @@ class TestAuthorizationService(BaseTest):
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
) -> None: ) -> None:
"""Test_can_refresh_permissions."""
user = self.find_or_create_user(username="user_one") user = self.find_or_create_user(username="user_one")
user_two = self.find_or_create_user(username="user_two") user_two = self.find_or_create_user(username="user_two")
admin_user = self.find_or_create_user(username="testadmin1") admin_user = self.find_or_create_user(username="testadmin1")
@ -399,7 +425,7 @@ class TestAuthorizationService(BaseTest):
GroupService.find_or_create_group("group_three") GroupService.find_or_create_group("group_three")
assert GroupModel.query.filter_by(identifier="group_three").first() is not None assert GroupModel.query.filter_by(identifier="group_three").first() is not None
group_info = [ group_info: list[GroupPermissionsDict] = [
{ {
"users": ["user_one", "user_two"], "users": ["user_one", "user_two"],
"name": "group_one", "name": "group_one",
@ -410,14 +436,20 @@ class TestAuthorizationService(BaseTest):
"name": "group_three", "name": "group_three",
"permissions": [{"actions": ["create", "read"], "uri": "PG:hey2"}], "permissions": [{"actions": ["create", "read"], "uri": "PG:hey2"}],
}, },
{
"users": [],
"name": "everybody",
"permissions": [{"actions": ["read"], "uri": "PG:hey2everybody"}],
},
] ]
AuthorizationService.refresh_permissions(group_info) AuthorizationService.refresh_permissions(group_info)
assert GroupModel.query.filter_by(identifier="group_two").first() is None assert GroupModel.query.filter_by(identifier="group_two").first() is None
assert GroupModel.query.filter_by(identifier="group_one").first() is not None assert GroupModel.query.filter_by(identifier="group_one").first() is not None
self.assert_user_has_permission(admin_user, "create", "/anything-they-want") self.assert_user_has_permission(admin_user, "create", "/v1.0/process-groups/whatever")
self.assert_user_has_permission(user, "read", "/v1.0/process-groups/hey") self.assert_user_has_permission(user, "read", "/v1.0/process-groups/hey")
self.assert_user_has_permission(user, "read", "/v1.0/process-groups/hey:yo") self.assert_user_has_permission(user, "read", "/v1.0/process-groups/hey:yo")
self.assert_user_has_permission(user, "create", "/v1.0/process-groups/hey:yo") self.assert_user_has_permission(user, "create", "/v1.0/process-groups/hey:yo")
self.assert_user_has_permission(user, "read", "/v1.0/process-groups/hey2everybody:yo")
self.assert_user_has_permission(user_two, "read", "/v1.0/process-groups/hey") self.assert_user_has_permission(user_two, "read", "/v1.0/process-groups/hey")
self.assert_user_has_permission(user_two, "read", "/v1.0/process-groups/hey:yo") self.assert_user_has_permission(user_two, "read", "/v1.0/process-groups/hey:yo")
@ -445,7 +477,7 @@ class TestAuthorizationService(BaseTest):
self.assert_user_has_permission(user, "read", "/v1.0/process-groups/hey") self.assert_user_has_permission(user, "read", "/v1.0/process-groups/hey")
self.assert_user_has_permission(user, "read", "/v1.0/process-groups/hey:yo") self.assert_user_has_permission(user, "read", "/v1.0/process-groups/hey:yo")
self.assert_user_has_permission(user, "create", "/v1.0/process-groups/hey:yo", expected_result=False) self.assert_user_has_permission(user, "create", "/v1.0/process-groups/hey:yo", expected_result=False)
self.assert_user_has_permission(admin_user, "create", "/anything-they-want") self.assert_user_has_permission(admin_user, "create", "/v1.0/process-groups/whatever")
self.assert_user_has_permission(user_two, "read", "/v1.0/process-groups/hey", expected_result=False) self.assert_user_has_permission(user_two, "read", "/v1.0/process-groups/hey", expected_result=False)
assert GroupModel.query.filter_by(identifier="group_three").first() is not None assert GroupModel.query.filter_by(identifier="group_three").first() is not None

View File

@ -2,8 +2,8 @@
from flask.app import Flask from flask.app import Flask
from flask.testing import FlaskClient from flask.testing import FlaskClient
from tests.spiffworkflow_backend.helpers.base_test import BaseTest from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.process_instance_processor import ( from spiffworkflow_backend.services.process_instance_processor import (
ProcessInstanceProcessor, ProcessInstanceProcessor,
) )
@ -18,29 +18,17 @@ class TestDotNotation(BaseTest):
app: Flask, app: Flask,
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
"""Test_form_data_conversion_to_dot_dict.""" process_model_id = "dot_notation_group/test_dot_notation"
process_group_id = "dot_notation_group"
process_model_id = "test_dot_notation"
bpmn_file_name = "diagram.bpmn" bpmn_file_name = "diagram.bpmn"
bpmn_file_location = "dot_notation" bpmn_file_location = "dot_notation"
process_model_identifier = self.create_group_and_model_with_bpmn( process_model = load_test_spec(
client,
with_super_admin_user,
process_group_id=process_group_id,
process_model_id=process_model_id, process_model_id=process_model_id,
bpmn_file_name=bpmn_file_name, bpmn_file_name=bpmn_file_name,
bpmn_file_location=bpmn_file_location, process_model_source_directory=bpmn_file_location,
) )
headers = self.logged_in_headers(with_super_admin_user) process_instance = self.create_process_instance_from_process_model(process_model)
response = self.create_process_instance_from_process_model_id_with_api(
client, process_model_identifier, headers
)
process_instance_id = response.json["id"]
process_instance = ProcessInstanceService().get_process_instance(process_instance_id)
processor = ProcessInstanceProcessor(process_instance) processor = ProcessInstanceProcessor(process_instance)
processor.do_engine_steps(save=True) processor.do_engine_steps(save=True)
human_task = process_instance.human_tasks[0] human_task = process_instance.human_tasks[0]
@ -53,7 +41,9 @@ class TestDotNotation(BaseTest):
"invoice.invoiceAmount": "1000.00", "invoice.invoiceAmount": "1000.00",
"invoice.dueDate": "09/30/2022", "invoice.dueDate": "09/30/2022",
} }
ProcessInstanceService.complete_form_task(processor, user_task, form_data, with_super_admin_user, human_task) ProcessInstanceService.complete_form_task(
processor, user_task, form_data, process_instance.process_initiator, human_task
)
expected = { expected = {
"contibutorName": "Elizabeth", "contibutorName": "Elizabeth",

View File

@ -9,14 +9,10 @@ from spiffworkflow_backend.models.message_instance import MessageInstanceModel
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus
from spiffworkflow_backend.models.process_model import ProcessModelInfo from spiffworkflow_backend.models.process_model import ProcessModelInfo
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.error_handling_service import ErrorHandlingService from spiffworkflow_backend.services.error_handling_service import ErrorHandlingService
from spiffworkflow_backend.services.process_instance_processor import ( from spiffworkflow_backend.services.process_instance_processor import (
ProcessInstanceProcessor, ProcessInstanceProcessor,
) )
from spiffworkflow_backend.services.process_instance_service import (
ProcessInstanceService,
)
from spiffworkflow_backend.services.process_model_service import ProcessModelService from spiffworkflow_backend.services.process_model_service import ProcessModelService
from spiffworkflow_backend.services.workflow_execution_service import WorkflowExecutionServiceError from spiffworkflow_backend.services.workflow_execution_service import WorkflowExecutionServiceError
@ -27,12 +23,8 @@ class TestErrorHandlingService(BaseTest):
Like it can fire off BPMN messages in case a BPMN Task is waiting for that message. Like it can fire off BPMN messages in case a BPMN Task is waiting for that message.
""" """
def run_process_model_and_handle_error( def run_process_model_and_handle_error(self, process_model: ProcessModelInfo) -> ProcessInstanceModel:
self, process_model: ProcessModelInfo, user: UserModel process_instance = self.create_process_instance_from_process_model(process_model)
) -> ProcessInstanceModel:
process_instance = ProcessInstanceService.create_process_instance_from_process_model_identifier(
process_model.id, user
)
pip = ProcessInstanceProcessor(process_instance) pip = ProcessInstanceProcessor(process_instance)
with pytest.raises(WorkflowExecutionServiceError) as e: with pytest.raises(WorkflowExecutionServiceError) as e:
pip.do_engine_steps(save=True) pip.do_engine_steps(save=True)
@ -44,7 +36,6 @@ class TestErrorHandlingService(BaseTest):
app: Flask, app: Flask,
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
"""Process Model in DB marked as suspended when error occurs.""" """Process Model in DB marked as suspended when error occurs."""
process_model = load_test_spec( process_model = load_test_spec(
@ -54,13 +45,13 @@ class TestErrorHandlingService(BaseTest):
) )
# Process instance should be marked as errored by default. # Process instance should be marked as errored by default.
process_instance = self.run_process_model_and_handle_error(process_model, with_super_admin_user) process_instance = self.run_process_model_and_handle_error(process_model)
assert ProcessInstanceStatus.error.value == process_instance.status assert ProcessInstanceStatus.error.value == process_instance.status
# If process model should be suspended on error, then that is what should happen. # If process model should be suspended on error, then that is what should happen.
process_model.fault_or_suspend_on_exception = "suspend" process_model.fault_or_suspend_on_exception = "suspend"
ProcessModelService.save_process_model(process_model) ProcessModelService.save_process_model(process_model)
process_instance = self.run_process_model_and_handle_error(process_model, with_super_admin_user) process_instance = self.run_process_model_and_handle_error(process_model)
assert ProcessInstanceStatus.suspended.value == process_instance.status assert ProcessInstanceStatus.suspended.value == process_instance.status
def test_error_sends_bpmn_message( def test_error_sends_bpmn_message(
@ -68,7 +59,6 @@ class TestErrorHandlingService(BaseTest):
app: Flask, app: Flask,
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
"""Real BPMN Messages should get generated and processes should fire off and complete.""" """Real BPMN Messages should get generated and processes should fire off and complete."""
process_model = load_test_spec( process_model = load_test_spec(
@ -85,7 +75,7 @@ class TestErrorHandlingService(BaseTest):
process_model.exception_notification_addresses = ["dan@ILoveToReadErrorsInMyEmails.com"] process_model.exception_notification_addresses = ["dan@ILoveToReadErrorsInMyEmails.com"]
ProcessModelService.save_process_model(process_model) ProcessModelService.save_process_model(process_model)
# kick off the process and assure it got marked as an error. # kick off the process and assure it got marked as an error.
process_instance = self.run_process_model_and_handle_error(process_model, with_super_admin_user) process_instance = self.run_process_model_and_handle_error(process_model)
assert ProcessInstanceStatus.error.value == process_instance.status assert ProcessInstanceStatus.error.value == process_instance.status
# Both send and receive messages should be generated, matched # Both send and receive messages should be generated, matched

View File

@ -3,44 +3,33 @@ import pytest
from flask import Flask from flask import Flask
from flask.testing import FlaskClient from flask.testing import FlaskClient
from tests.spiffworkflow_backend.helpers.base_test import BaseTest from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
from spiffworkflow_backend.models.db import db from spiffworkflow_backend.models.db import db
from spiffworkflow_backend.models.message_instance import MessageInstanceModel from spiffworkflow_backend.models.message_instance import MessageInstanceModel
from spiffworkflow_backend.models.user import UserModel from spiffworkflow_backend.models.process_model import ProcessModelInfo
from spiffworkflow_backend.services.process_model_service import ProcessModelService
class TestMessageInstance(BaseTest): class TestMessageInstance(BaseTest):
"""TestMessageInstance.""" def setup_message_tests(self, client: FlaskClient) -> ProcessModelInfo:
process_model_id = "testk_group/hello_world"
def setup_message_tests(self, client: FlaskClient, user: UserModel) -> str:
"""Setup_message_tests."""
process_group_id = "test_group"
process_model_id = "hello_world"
bpmn_file_name = "hello_world.bpmn" bpmn_file_name = "hello_world.bpmn"
bpmn_file_location = "hello_world" bpmn_file_location = "hello_world"
process_model_identifier = self.create_group_and_model_with_bpmn( process_model = load_test_spec(
client,
user,
process_group_id=process_group_id,
process_model_id=process_model_id, process_model_id=process_model_id,
bpmn_file_name=bpmn_file_name, bpmn_file_name=bpmn_file_name,
bpmn_file_location=bpmn_file_location, process_model_source_directory=bpmn_file_location,
) )
return process_model_identifier return process_model
def test_can_create_message_instance( def test_can_create_message_instance(
self, self,
app: Flask, app: Flask,
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
"""Test_can_create_message_instance."""
message_name = "Message Model One" message_name = "Message Model One"
process_model_identifier = self.setup_message_tests(client, with_super_admin_user) process_model = self.setup_message_tests(client)
process_model = ProcessModelService.get_process_model(process_model_id=process_model_identifier)
process_instance = self.create_process_instance_from_process_model(process_model, "waiting") process_instance = self.create_process_instance_from_process_model(process_model, "waiting")
queued_message = MessageInstanceModel( queued_message = MessageInstanceModel(
@ -64,12 +53,9 @@ class TestMessageInstance(BaseTest):
app: Flask, app: Flask,
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
"""Test_cannot_set_invalid_status."""
message_name = "message_model_one" message_name = "message_model_one"
process_model_identifier = self.setup_message_tests(client, with_super_admin_user) process_model = self.setup_message_tests(client)
process_model = ProcessModelService.get_process_model(process_model_id=process_model_identifier)
process_instance = self.create_process_instance_from_process_model(process_model, "waiting") process_instance = self.create_process_instance_from_process_model(process_model, "waiting")
with pytest.raises(ValueError) as exception: with pytest.raises(ValueError) as exception:
@ -100,13 +86,9 @@ class TestMessageInstance(BaseTest):
app: Flask, app: Flask,
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
"""Test_cannot_set_invalid_message_type."""
message_name = "message_model_one" message_name = "message_model_one"
process_model_identifier = self.setup_message_tests(client, with_super_admin_user) process_model = self.setup_message_tests(client)
process_model = ProcessModelService.get_process_model(process_model_id=process_model_identifier)
process_instance = self.create_process_instance_from_process_model(process_model, "waiting") process_instance = self.create_process_instance_from_process_model(process_model, "waiting")
with pytest.raises(ValueError) as exception: with pytest.raises(ValueError) as exception:
@ -136,13 +118,9 @@ class TestMessageInstance(BaseTest):
app: Flask, app: Flask,
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
"""Test_force_failure_cause_if_status_is_failure."""
message_name = "message_model_one" message_name = "message_model_one"
process_model_identifier = self.setup_message_tests(client, with_super_admin_user) process_model = self.setup_message_tests(client)
process_model = ProcessModelService.get_process_model(process_model_id=process_model_identifier)
process_instance = self.create_process_instance_from_process_model(process_model, "waiting") process_instance = self.create_process_instance_from_process_model(process_model, "waiting")
queued_message = MessageInstanceModel( queued_message = MessageInstanceModel(

View File

@ -1,15 +1,10 @@
"""Test_message_service."""
import pytest
from flask import Flask from flask import Flask
from flask.testing import FlaskClient from flask.testing import FlaskClient
from tests.spiffworkflow_backend.helpers.base_test import BaseTest from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
from spiffworkflow_backend.exceptions.api_error import ApiError
from spiffworkflow_backend.models.message_instance import MessageInstanceModel from spiffworkflow_backend.models.message_instance import MessageInstanceModel
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.routes.messages_controller import message_send
from spiffworkflow_backend.services.message_service import MessageService from spiffworkflow_backend.services.message_service import MessageService
from spiffworkflow_backend.services.process_instance_processor import ( from spiffworkflow_backend.services.process_instance_processor import (
ProcessInstanceProcessor, ProcessInstanceProcessor,
@ -20,66 +15,11 @@ from spiffworkflow_backend.services.process_instance_service import (
class TestMessageService(BaseTest): class TestMessageService(BaseTest):
"""TestMessageService."""
def test_message_from_api_into_running_process(
self,
app: Flask,
client: FlaskClient,
with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None:
"""Test sending a message to a running process via the API.
This example workflow will send a message called 'request_approval' and then wait for a response message
of 'approval_result'. This test assures that it will fire the message with the correct correlation properties
and will respond only to a message called 'approval_result' that has the matching correlation properties,
as sent by an API Call.
"""
self.payload = {
"customer_id": "Sartography",
"po_number": 1001,
"description": "We built a new feature for messages!",
"amount": "100.00",
}
self.start_sender_process(client, with_super_admin_user, "test_from_api")
self.assure_a_message_was_sent()
self.assure_there_is_a_process_waiting_on_a_message()
# Make an API call to the service endpoint, but use the wrong po number
with pytest.raises(ApiError):
message_send("Approval Result", {"payload": {"po_number": 5001}})
# Should return an error when making an API call for right po number, wrong client
with pytest.raises(ApiError):
message_send(
"Approval Result",
{"payload": {"po_number": 1001, "customer_id": "jon"}},
)
# No error when calling with the correct parameters
message_send(
"Approval Result",
{"payload": {"po_number": 1001, "customer_id": "Sartography"}},
)
# There is no longer a waiting message
waiting_messages = (
MessageInstanceModel.query.filter_by(message_type="receive")
.filter_by(status="ready")
.filter_by(process_instance_id=self.process_instance.id)
.all()
)
assert len(waiting_messages) == 0
# The process has completed
assert self.process_instance.status == "complete"
def test_single_conversation_between_two_processes( def test_single_conversation_between_two_processes(
self, self,
app: Flask, app: Flask,
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
"""Test messages between two different running processes using a single conversation. """Test messages between two different running processes using a single conversation.
@ -87,7 +27,7 @@ class TestMessageService(BaseTest):
we have two process instances that are communicating with each other using one conversation about an we have two process instances that are communicating with each other using one conversation about an
Invoice whose details are defined in the following message payload Invoice whose details are defined in the following message payload
""" """
self.payload = { payload = {
"customer_id": "Sartography", "customer_id": "Sartography",
"po_number": 1001, "po_number": 1001,
"description": "We built a new feature for messages!", "description": "We built a new feature for messages!",
@ -104,8 +44,8 @@ class TestMessageService(BaseTest):
) )
# Now start the main process # Now start the main process
self.start_sender_process(client, with_super_admin_user, "test_between_processes") process_instance = self.start_sender_process(client, payload, "test_between_processes")
self.assure_a_message_was_sent() self.assure_a_message_was_sent(process_instance, payload)
# This is typically called in a background cron process, so we will manually call it # This is typically called in a background cron process, so we will manually call it
# here in the tests # here in the tests
@ -113,7 +53,7 @@ class TestMessageService(BaseTest):
MessageService.correlate_all_message_instances() MessageService.correlate_all_message_instances()
# The sender process should still be waiting on a message to be returned to it ... # The sender process should still be waiting on a message to be returned to it ...
self.assure_there_is_a_process_waiting_on_a_message() self.assure_there_is_a_process_waiting_on_a_message(process_instance)
# The second time we call ths process_message_isntances (again it would typically be running on cron) # The second time we call ths process_message_isntances (again it would typically be running on cron)
# it will deliver the message that was sent from the receiver back to the original sender. # it will deliver the message that was sent from the receiver back to the original sender.
@ -125,7 +65,7 @@ class TestMessageService(BaseTest):
waiting_messages = ( waiting_messages = (
MessageInstanceModel.query.filter_by(message_type="receive") MessageInstanceModel.query.filter_by(message_type="receive")
.filter_by(status="ready") .filter_by(status="ready")
.filter_by(process_instance_id=self.process_instance.id) .filter_by(process_instance_id=process_instance.id)
.order_by(MessageInstanceModel.id) .order_by(MessageInstanceModel.id)
.all() .all()
) )
@ -136,7 +76,7 @@ class TestMessageService(BaseTest):
assert len(waiting_messages) == 0 assert len(waiting_messages) == 0
# The message sender process is complete # The message sender process is complete
assert self.process_instance.status == "complete" assert process_instance.status == "complete"
# The message receiver process is also complete # The message receiver process is also complete
message_receiver_process = ( message_receiver_process = (
@ -146,83 +86,14 @@ class TestMessageService(BaseTest):
) )
assert message_receiver_process.status == "complete" assert message_receiver_process.status == "complete"
def start_sender_process(
self,
client: FlaskClient,
with_super_admin_user: UserModel,
group_name: str = "test_group",
) -> None:
process_group_id = group_name
self.create_process_group_with_api(client, with_super_admin_user, process_group_id, process_group_id)
process_model = load_test_spec(
"test_group/message",
process_model_source_directory="message_send_one_conversation",
bpmn_file_name="message_sender.bpmn", # Slightly misnamed, it sends and receives
)
self.process_instance = ProcessInstanceService.create_process_instance_from_process_model_identifier(
process_model.id,
with_super_admin_user,
)
processor_send_receive = ProcessInstanceProcessor(self.process_instance)
processor_send_receive.do_engine_steps(save=True)
task = processor_send_receive.get_all_user_tasks()[0]
human_task = self.process_instance.active_human_tasks[0]
ProcessInstanceService.complete_form_task(
processor_send_receive,
task,
self.payload,
with_super_admin_user,
human_task,
)
processor_send_receive.save()
def assure_a_message_was_sent(self) -> None:
# There should be one new send message for the given process instance.
send_messages = (
MessageInstanceModel.query.filter_by(message_type="send")
.filter_by(process_instance_id=self.process_instance.id)
.order_by(MessageInstanceModel.id)
.all()
)
assert len(send_messages) == 1
send_message = send_messages[0]
assert send_message.payload == self.payload, "The send message should match up with the payload"
assert send_message.name == "Request Approval"
assert send_message.status == "ready"
def assure_there_is_a_process_waiting_on_a_message(self) -> None:
# There should be one new send message for the given process instance.
waiting_messages = (
MessageInstanceModel.query.filter_by(message_type="receive")
.filter_by(status="ready")
.filter_by(process_instance_id=self.process_instance.id)
.order_by(MessageInstanceModel.id)
.all()
)
assert len(waiting_messages) == 1
waiting_message = waiting_messages[0]
self.assure_correlation_properties_are_right(waiting_message)
def assure_correlation_properties_are_right(self, message: MessageInstanceModel) -> None:
# Correlation Properties should match up
po_curr = next(c for c in message.correlation_rules if c.name == "po_number")
customer_curr = next(c for c in message.correlation_rules if c.name == "customer_id")
assert po_curr is not None
assert customer_curr is not None
def test_can_send_message_to_multiple_process_models( def test_can_send_message_to_multiple_process_models(
self, self,
app: Flask, app: Flask,
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
"""Test_can_send_message_to_multiple_process_models.""" """Test_can_send_message_to_multiple_process_models."""
process_group_id = "test_group_multi" # self.create_process_group_with_api(client, with_super_admin_user, process_group_id, process_group_id)
self.create_process_group_with_api(client, with_super_admin_user, process_group_id, process_group_id)
process_model_sender = load_test_spec( process_model_sender = load_test_spec(
"test_group/message_sender", "test_group/message_sender",

View File

@ -1,4 +1,3 @@
"""Test Permissions."""
from flask.app import Flask from flask.app import Flask
from flask.testing import FlaskClient from flask.testing import FlaskClient
from tests.spiffworkflow_backend.helpers.base_test import BaseTest from tests.spiffworkflow_backend.helpers.base_test import BaseTest
@ -9,7 +8,6 @@ from spiffworkflow_backend.models.group import GroupModel
from spiffworkflow_backend.models.permission_assignment import PermissionAssignmentModel from spiffworkflow_backend.models.permission_assignment import PermissionAssignmentModel
from spiffworkflow_backend.models.permission_target import PermissionTargetModel from spiffworkflow_backend.models.permission_target import PermissionTargetModel
from spiffworkflow_backend.models.principal import PrincipalModel from spiffworkflow_backend.models.principal import PrincipalModel
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.user_service import UserService from spiffworkflow_backend.services.user_service import UserService
@ -22,18 +20,13 @@ from spiffworkflow_backend.services.user_service import UserService
# * super-admins users maybe conventionally get the user role as well # * super-admins users maybe conventionally get the user role as well
# finance-admin role allows create, update, and delete of all models under the finance group # finance-admin role allows create, update, and delete of all models under the finance group
class TestPermissions(BaseTest): class TestPermissions(BaseTest):
"""TestPermissions."""
def test_user_can_be_given_permission_to_administer_process_group( def test_user_can_be_given_permission_to_administer_process_group(
self, self,
app: Flask, app: Flask,
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
"""Test_user_can_be_given_permission_to_administer_process_group."""
process_group_id = "group-a" process_group_id = "group-a"
self.create_process_group_with_api(client, with_super_admin_user, process_group_id, process_group_id)
load_test_spec( load_test_spec(
"group-a/timers_intermediate_catch_event", "group-a/timers_intermediate_catch_event",
bpmn_file_name="timers_intermediate_catch_event.bpmn", bpmn_file_name="timers_intermediate_catch_event.bpmn",
@ -58,7 +51,6 @@ class TestPermissions(BaseTest):
def test_group_a_admin_needs_to_stay_away_from_group_b( def test_group_a_admin_needs_to_stay_away_from_group_b(
self, app: Flask, with_db_and_bpmn_file_cleanup: None self, app: Flask, with_db_and_bpmn_file_cleanup: None
) -> None: ) -> None:
"""Test_group_a_admin_needs_to_stay_away_from_group_b."""
process_group_ids = ["group-a", "group-b"] process_group_ids = ["group-a", "group-b"]
process_group_a_id = process_group_ids[0] process_group_a_id = process_group_ids[0]
process_group_b_id = process_group_ids[1] process_group_b_id = process_group_ids[1]
@ -87,7 +79,6 @@ class TestPermissions(BaseTest):
self.assert_user_has_permission(group_a_admin, "update", f"/{process_group_b_id}", expected_result=False) self.assert_user_has_permission(group_a_admin, "update", f"/{process_group_b_id}", expected_result=False)
def test_user_can_be_granted_access_through_a_group(self, app: Flask, with_db_and_bpmn_file_cleanup: None) -> None: def test_user_can_be_granted_access_through_a_group(self, app: Flask, with_db_and_bpmn_file_cleanup: None) -> None:
"""Test_user_can_be_granted_access_through_a_group."""
process_group_ids = ["group-a", "group-b"] process_group_ids = ["group-a", "group-b"]
process_group_a_id = process_group_ids[0] process_group_a_id = process_group_ids[0]
for process_group_id in process_group_ids: for process_group_id in process_group_ids:
@ -125,7 +116,6 @@ class TestPermissions(BaseTest):
def test_user_can_be_read_models_with_global_permission( def test_user_can_be_read_models_with_global_permission(
self, app: Flask, with_db_and_bpmn_file_cleanup: None self, app: Flask, with_db_and_bpmn_file_cleanup: None
) -> None: ) -> None:
"""Test_user_can_be_read_models_with_global_permission."""
process_group_ids = ["group-a", "group-b"] process_group_ids = ["group-a", "group-b"]
process_group_a_id = process_group_ids[0] process_group_a_id = process_group_ids[0]
process_group_b_id = process_group_ids[1] process_group_b_id = process_group_ids[1]
@ -156,7 +146,6 @@ class TestPermissions(BaseTest):
def test_user_can_access_base_path_when_given_wildcard_permission( def test_user_can_access_base_path_when_given_wildcard_permission(
self, app: Flask, with_db_and_bpmn_file_cleanup: None self, app: Flask, with_db_and_bpmn_file_cleanup: None
) -> None: ) -> None:
"""Test_user_can_access_base_path_when_given_wildcard_permission."""
group_a_admin = self.find_or_create_user() group_a_admin = self.find_or_create_user()
permission_target = PermissionTargetModel(uri="/process-models/%") permission_target = PermissionTargetModel(uri="/process-models/%")

View File

@ -18,7 +18,6 @@ from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus
from spiffworkflow_backend.models.process_instance_event import ProcessInstanceEventType from spiffworkflow_backend.models.process_instance_event import ProcessInstanceEventType
from spiffworkflow_backend.models.task import TaskModel # noqa: F401 from spiffworkflow_backend.models.task import TaskModel # noqa: F401
from spiffworkflow_backend.models.task_definition import TaskDefinitionModel from spiffworkflow_backend.models.task_definition import TaskDefinitionModel
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.authorization_service import AuthorizationService from spiffworkflow_backend.services.authorization_service import AuthorizationService
from spiffworkflow_backend.services.authorization_service import ( from spiffworkflow_backend.services.authorization_service import (
UserDoesNotHaveAccessToTaskError, UserDoesNotHaveAccessToTaskError,
@ -70,10 +69,9 @@ class TestProcessInstanceProcessor(BaseTest):
app: Flask, app: Flask,
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
"""Test_sets_permission_correctly_on_human_task.""" """Test_sets_permission_correctly_on_human_task."""
self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group") self.create_process_group("test_group", "test_group")
initiator_user = self.find_or_create_user("initiator_user") initiator_user = self.find_or_create_user("initiator_user")
finance_user = self.find_or_create_user("testuser2") finance_user = self.find_or_create_user("testuser2")
assert initiator_user.principal is not None assert initiator_user.principal is not None
@ -138,10 +136,9 @@ class TestProcessInstanceProcessor(BaseTest):
app: Flask, app: Flask,
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
"""Test_sets_permission_correctly_on_human_task_when_using_dict.""" """Test_sets_permission_correctly_on_human_task_when_using_dict."""
self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group") self.create_process_group("test_group", "test_group")
initiator_user = self.find_or_create_user("initiator_user") initiator_user = self.find_or_create_user("initiator_user")
finance_user_three = self.find_or_create_user("testuser3") finance_user_three = self.find_or_create_user("testuser3")
finance_user_four = self.find_or_create_user("testuser4") finance_user_four = self.find_or_create_user("testuser4")
@ -234,7 +231,6 @@ class TestProcessInstanceProcessor(BaseTest):
app: Flask, app: Flask,
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
"""Test_does_not_recreate_human_tasks_on_multiple_saves.""" """Test_does_not_recreate_human_tasks_on_multiple_saves."""
initiator_user = self.find_or_create_user("initiator_user") initiator_user = self.find_or_create_user("initiator_user")
@ -264,9 +260,8 @@ class TestProcessInstanceProcessor(BaseTest):
app: Flask, app: Flask,
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group") self.create_process_group("test_group", "test_group")
initiator_user = self.find_or_create_user("initiator_user") initiator_user = self.find_or_create_user("initiator_user")
finance_user_three = self.find_or_create_user("testuser3") finance_user_three = self.find_or_create_user("testuser3")
assert initiator_user.principal is not None assert initiator_user.principal is not None
@ -319,9 +314,8 @@ class TestProcessInstanceProcessor(BaseTest):
app: Flask, app: Flask,
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group") self.create_process_group("test_group", "test_group")
initiator_user = self.find_or_create_user("initiator_user") initiator_user = self.find_or_create_user("initiator_user")
finance_user_three = self.find_or_create_user("testuser3") finance_user_three = self.find_or_create_user("testuser3")
assert initiator_user.principal is not None assert initiator_user.principal is not None
@ -439,23 +433,20 @@ class TestProcessInstanceProcessor(BaseTest):
app: Flask, app: Flask,
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group") self.create_process_group("test_group", "test_group")
process_model = load_test_spec( process_model = load_test_spec(
process_model_id="test_group/boundary_event_reset", process_model_id="test_group/boundary_event_reset",
process_model_source_directory="boundary_event_reset", process_model_source_directory="boundary_event_reset",
) )
process_instance = self.create_process_instance_from_process_model( process_instance = self.create_process_instance_from_process_model(process_model=process_model)
process_model=process_model, user=with_super_admin_user
)
processor = ProcessInstanceProcessor(process_instance) processor = ProcessInstanceProcessor(process_instance)
processor.do_engine_steps(save=True) processor.do_engine_steps(save=True)
assert len(process_instance.active_human_tasks) == 1 assert len(process_instance.active_human_tasks) == 1
human_task_one = process_instance.active_human_tasks[0] human_task_one = process_instance.active_human_tasks[0]
spiff_manual_task = processor.bpmn_process_instance.get_task_from_id(UUID(human_task_one.task_id)) spiff_manual_task = processor.bpmn_process_instance.get_task_from_id(UUID(human_task_one.task_id))
ProcessInstanceService.complete_form_task( ProcessInstanceService.complete_form_task(
processor, spiff_manual_task, {}, with_super_admin_user, human_task_one processor, spiff_manual_task, {}, process_instance.process_initiator, human_task_one
) )
assert ( assert (
len(process_instance.active_human_tasks) == 1 len(process_instance.active_human_tasks) == 1
@ -473,7 +464,9 @@ class TestProcessInstanceProcessor(BaseTest):
human_task_one = process_instance.active_human_tasks[0] human_task_one = process_instance.active_human_tasks[0]
assert human_task_one.task_title == "Manual Task #1" assert human_task_one.task_title == "Manual Task #1"
processor = ProcessInstanceProcessor(process_instance) processor = ProcessInstanceProcessor(process_instance)
processor.manual_complete_task(str(human_task_one.task_id), execute=True) processor.manual_complete_task(
str(human_task_one.task_id), execute=True, user=process_instance.process_initiator
)
processor = ProcessInstanceProcessor(process_instance) processor = ProcessInstanceProcessor(process_instance)
processor.resume() processor.resume()
processor.do_engine_steps(save=True) processor.do_engine_steps(save=True)
@ -490,22 +483,21 @@ class TestProcessInstanceProcessor(BaseTest):
app: Flask, app: Flask,
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group") self.create_process_group("test_group", "test_group")
process_model = load_test_spec( process_model = load_test_spec(
process_model_id="test_group/step_through_gateway", process_model_id="test_group/step_through_gateway",
process_model_source_directory="step_through_gateway", process_model_source_directory="step_through_gateway",
) )
process_instance = self.create_process_instance_from_process_model( process_instance = self.create_process_instance_from_process_model(process_model=process_model)
process_model=process_model, user=with_super_admin_user
)
processor = ProcessInstanceProcessor(process_instance) processor = ProcessInstanceProcessor(process_instance)
processor.do_engine_steps(save=True) processor.do_engine_steps(save=True)
assert len(process_instance.active_human_tasks) == 1 assert len(process_instance.active_human_tasks) == 1
human_task_one = process_instance.active_human_tasks[0] human_task_one = process_instance.active_human_tasks[0]
processor.bpmn_process_instance.get_task_from_id(UUID(human_task_one.task_id)) processor.bpmn_process_instance.get_task_from_id(UUID(human_task_one.task_id))
processor.manual_complete_task(str(human_task_one.task_id), execute=True) processor.manual_complete_task(
str(human_task_one.task_id), execute=True, user=process_instance.process_initiator
)
processor.save() processor.save()
processor = ProcessInstanceProcessor(process_instance) processor = ProcessInstanceProcessor(process_instance)
step1_task = processor.get_task_by_bpmn_identifier("step_1", processor.bpmn_process_instance) step1_task = processor.get_task_by_bpmn_identifier("step_1", processor.bpmn_process_instance)
@ -516,7 +508,7 @@ class TestProcessInstanceProcessor(BaseTest):
assert gateway_task.state == TaskState.READY assert gateway_task.state == TaskState.READY
gateway_task = processor.bpmn_process_instance.get_tasks(TaskState.READY)[0] gateway_task = processor.bpmn_process_instance.get_tasks(TaskState.READY)[0]
processor.manual_complete_task(str(gateway_task.id), execute=True) processor.manual_complete_task(str(gateway_task.id), execute=True, user=process_instance.process_initiator)
processor.save() processor.save()
processor = ProcessInstanceProcessor(process_instance) processor = ProcessInstanceProcessor(process_instance)
gateway_task = processor.get_task_by_bpmn_identifier("Gateway_Open", processor.bpmn_process_instance) gateway_task = processor.get_task_by_bpmn_identifier("Gateway_Open", processor.bpmn_process_instance)
@ -528,9 +520,8 @@ class TestProcessInstanceProcessor(BaseTest):
app: Flask, app: Flask,
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group") self.create_process_group("test_group", "test_group")
initiator_user = self.find_or_create_user("initiator_user") initiator_user = self.find_or_create_user("initiator_user")
finance_user_three = self.find_or_create_user("testuser3") finance_user_three = self.find_or_create_user("testuser3")
assert initiator_user.principal is not None assert initiator_user.principal is not None
@ -757,10 +748,9 @@ class TestProcessInstanceProcessor(BaseTest):
app: Flask, app: Flask,
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
"""Test_does_not_recreate_human_tasks_on_multiple_saves.""" """Test_does_not_recreate_human_tasks_on_multiple_saves."""
self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group") self.create_process_group("test_group", "test_group")
initiator_user = self.find_or_create_user("initiator_user") initiator_user = self.find_or_create_user("initiator_user")
finance_user_three = self.find_or_create_user("testuser3") finance_user_three = self.find_or_create_user("testuser3")
assert initiator_user.principal is not None assert initiator_user.principal is not None
@ -868,7 +858,6 @@ class TestProcessInstanceProcessor(BaseTest):
app: Flask, app: Flask,
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
"""Test_task_data_is_set_even_if_process_instance_errors.""" """Test_task_data_is_set_even_if_process_instance_errors."""
process_model = load_test_spec( process_model = load_test_spec(
@ -876,9 +865,7 @@ class TestProcessInstanceProcessor(BaseTest):
bpmn_file_name="script_error_with_task_data.bpmn", bpmn_file_name="script_error_with_task_data.bpmn",
process_model_source_directory="error", process_model_source_directory="error",
) )
process_instance = self.create_process_instance_from_process_model( process_instance = self.create_process_instance_from_process_model(process_model=process_model)
process_model=process_model, user=with_super_admin_user
)
processor = ProcessInstanceProcessor(process_instance) processor = ProcessInstanceProcessor(process_instance)
with pytest.raises(WorkflowExecutionServiceError): with pytest.raises(WorkflowExecutionServiceError):

View File

@ -1,141 +0,0 @@
# from typing import Optional
#
# from flask.app import Flask
# from tests.spiffworkflow_backend.helpers.base_test import BaseTest
#
# from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
# from spiffworkflow_backend.models.process_instance_report import (
# ProcessInstanceReportModel,
# )
#
# # from tests.spiffworkflow_backend.helpers.test_data import find_or_create_process_group
# # from spiffworkflow_backend.models.permission_assignment import PermissionAssignmentModel
# # from spiffworkflow_backend.models.permission_target import PermissionTargetModel
#
#
# def test_generate_report_with_filter_by(
# app: Flask,
# with_db_and_bpmn_file_cleanup: None,
# setup_process_instances_for_reports: list[ProcessInstanceModel],
# ) -> None:
# """Test_user_can_be_given_permission_to_administer_process_group."""
# process_instances = setup_process_instances_for_reports
# report_metadata = {
# "filter_by": [
# {"field_name": "grade_level", "operator": "equals", "field_value": 2}
# ]
# }
# results = do_report_with_metadata_and_instances(report_metadata, process_instances)
# assert len(results) == 2
# names = get_names_from_results(results)
# assert names == ["kay", "jay"]
#
#
# def test_generate_report_with_filter_by_with_variable_substitution(
# app: Flask,
# with_db_and_bpmn_file_cleanup: None,
# setup_process_instances_for_reports: list[ProcessInstanceModel],
# ) -> None:
# """Test_generate_report_with_filter_by_with_variable_substitution."""
# process_instances = setup_process_instances_for_reports
# report_metadata = {
# "filter_by": [
# {
# "field_name": "grade_level",
# "operator": "equals",
# "field_value": "{{grade_level}}",
# }
# ]
# }
# results = do_report_with_metadata_and_instances(
# report_metadata, process_instances, {"grade_level": 1}
# )
# assert len(results) == 1
# names = get_names_from_results(results)
# assert names == ["ray"]
#
#
# def test_generate_report_with_order_by_and_one_field(
# app: Flask,
# with_db_and_bpmn_file_cleanup: None,
# setup_process_instances_for_reports: list[ProcessInstanceModel],
# ) -> None:
# """Test_generate_report_with_order_by_and_one_field."""
# process_instances = setup_process_instances_for_reports
# report_metadata = {"order_by": ["test_score"]}
# results = do_report_with_metadata_and_instances(report_metadata, process_instances)
# assert len(results) == 3
# names = get_names_from_results(results)
# assert names == ["jay", "ray", "kay"]
#
#
# def test_generate_report_with_order_by_and_two_fields(
# app: Flask,
# with_db_and_bpmn_file_cleanup: None,
# setup_process_instances_for_reports: list[ProcessInstanceModel],
# ) -> None:
# """Test_generate_report_with_order_by_and_two_fields."""
# process_instances = setup_process_instances_for_reports
# report_metadata = {"order_by": ["grade_level", "test_score"]}
# results = do_report_with_metadata_and_instances(report_metadata, process_instances)
# assert len(results) == 3
# names = get_names_from_results(results)
# assert names == ["ray", "jay", "kay"]
#
#
# def test_generate_report_with_order_by_desc(
# app: Flask,
# with_db_and_bpmn_file_cleanup: None,
# setup_process_instances_for_reports: list[ProcessInstanceModel],
# ) -> None:
# """Test_generate_report_with_order_by_desc."""
# process_instances = setup_process_instances_for_reports
# report_metadata = {"order_by": ["grade_level", "-test_score"]}
# results = do_report_with_metadata_and_instances(report_metadata, process_instances)
# assert len(results) == 3
# names = get_names_from_results(results)
# assert names == ["ray", "kay", "jay"]
#
#
# def test_generate_report_with_columns(
# app: Flask,
# with_db_and_bpmn_file_cleanup: None,
# setup_process_instances_for_reports: list[ProcessInstanceModel],
# ) -> None:
# """Test_generate_report_with_columns."""
# process_instances = setup_process_instances_for_reports
# report_metadata = {
# "columns": [
# {"Header": "Name", "accessor": "name"},
# {"Header": "Status", "accessor": "status"},
# ],
# "order_by": ["test_score"],
# "filter_by": [
# {"field_name": "grade_level", "operator": "equals", "field_value": 1}
# ],
# }
# results = do_report_with_metadata_and_instances(report_metadata, process_instances)
# assert len(results) == 1
# assert results == [{"name": "ray", "status": "complete"}]
#
#
# def do_report_with_metadata_and_instances(
# report_metadata: dict,
# process_instances: list[ProcessInstanceModel],
# substitution_variables: Optional[dict] = None,
# ) -> list[dict]:
# """Do_report_with_metadata_and_instances."""
# process_instance_report = ProcessInstanceReportModel.create_report(
# identifier="sure",
# report_metadata=report_metadata,
# user=BaseTest.find_or_create_user(),
# )
#
# return process_instance_report.generate_report(
# process_instances, substitution_variables
# )["results"]
#
#
# def get_names_from_results(results: list[dict]) -> list[str]:
# """Get_names_from_results."""
# return [result["name"] for result in results]

View File

@ -9,7 +9,6 @@ from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from spiffworkflow_backend.models.process_instance_file_data import ( from spiffworkflow_backend.models.process_instance_file_data import (
ProcessInstanceFileDataModel, ProcessInstanceFileDataModel,
) )
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.process_instance_service import ( from spiffworkflow_backend.services.process_instance_service import (
ProcessInstanceService, ProcessInstanceService,
) )
@ -40,7 +39,6 @@ class TestProcessInstanceService(BaseTest):
self, self,
app: Flask, app: Flask,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
model = ProcessInstanceService.file_data_model_for_value( model = ProcessInstanceService.file_data_model_for_value(
"uploaded_file", "uploaded_file",
@ -53,7 +51,6 @@ class TestProcessInstanceService(BaseTest):
self, self,
app: Flask, app: Flask,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
model = ProcessInstanceService.file_data_model_for_value( model = ProcessInstanceService.file_data_model_for_value(
"not_a_file", "not_a_file",
@ -66,7 +63,6 @@ class TestProcessInstanceService(BaseTest):
self, self,
app: Flask, app: Flask,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
data = { data = {
"uploaded_file": self.SAMPLE_FILE_DATA, "uploaded_file": self.SAMPLE_FILE_DATA,
@ -80,7 +76,6 @@ class TestProcessInstanceService(BaseTest):
self, self,
app: Flask, app: Flask,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
data = { data = {
"uploaded_files": [self.SAMPLE_FILE_DATA, self.SAMPLE_FILE_DATA], "uploaded_files": [self.SAMPLE_FILE_DATA, self.SAMPLE_FILE_DATA],
@ -95,7 +90,6 @@ class TestProcessInstanceService(BaseTest):
self, self,
app: Flask, app: Flask,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
data = { data = {
"not_a_file": "just a value", "not_a_file": "just a value",
@ -120,7 +114,6 @@ class TestProcessInstanceService(BaseTest):
self, self,
app: Flask, app: Flask,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
data = { data = {
"not_a_file": "just a value", "not_a_file": "just a value",
@ -135,7 +128,6 @@ class TestProcessInstanceService(BaseTest):
self, self,
app: Flask, app: Flask,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
data = { data = {
"uploaded_file": self.SAMPLE_FILE_DATA, "uploaded_file": self.SAMPLE_FILE_DATA,
@ -156,7 +148,6 @@ class TestProcessInstanceService(BaseTest):
self, self,
app: Flask, app: Flask,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
data = { data = {
"not_a_file": "just a value", "not_a_file": "just a value",
@ -171,7 +162,6 @@ class TestProcessInstanceService(BaseTest):
self, self,
app: Flask, app: Flask,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
data = { data = {
"not_a_file": "just a value", "not_a_file": "just a value",
@ -198,7 +188,6 @@ class TestProcessInstanceService(BaseTest):
self, self,
app: Flask, app: Flask,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
data = { data = {
"File": [ "File": [

View File

@ -1,11 +1,9 @@
"""Test_various_bpmn_constructs."""
import pytest import pytest
from flask.app import Flask from flask.app import Flask
from flask.testing import FlaskClient from flask.testing import FlaskClient
from tests.spiffworkflow_backend.helpers.base_test import BaseTest from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.process_instance_processor import ( from spiffworkflow_backend.services.process_instance_processor import (
ProcessInstanceProcessor, ProcessInstanceProcessor,
) )
@ -18,16 +16,12 @@ class TestRestrictedScriptEngine(BaseTest):
app: Flask, app: Flask,
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group")
process_model = load_test_spec( process_model = load_test_spec(
"test_group/dangerous", "test_group/dangerous",
bpmn_file_name="read_etc_passwd.bpmn", bpmn_file_name="read_etc_passwd.bpmn",
process_model_source_directory="dangerous-scripts", process_model_source_directory="dangerous-scripts",
) )
self.find_or_create_user()
process_instance = self.create_process_instance_from_process_model(process_model) process_instance = self.create_process_instance_from_process_model(process_model)
processor = ProcessInstanceProcessor(process_instance) processor = ProcessInstanceProcessor(process_instance)
@ -40,16 +34,12 @@ class TestRestrictedScriptEngine(BaseTest):
app: Flask, app: Flask,
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group")
process_model = load_test_spec( process_model = load_test_spec(
"test_group/dangerous", "test_group/dangerous",
bpmn_file_name="read_env.bpmn", bpmn_file_name="read_env.bpmn",
process_model_source_directory="dangerous-scripts", process_model_source_directory="dangerous-scripts",
) )
self.find_or_create_user()
process_instance = self.create_process_instance_from_process_model(process_model) process_instance = self.create_process_instance_from_process_model(process_model)
processor = ProcessInstanceProcessor(process_instance) processor = ProcessInstanceProcessor(process_instance)

View File

@ -1,10 +1,8 @@
"""Test Permissions."""
from flask.app import Flask from flask.app import Flask
from flask.testing import FlaskClient from flask.testing import FlaskClient
from tests.spiffworkflow_backend.helpers.base_test import BaseTest from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.process_instance_processor import ( from spiffworkflow_backend.services.process_instance_processor import (
ProcessInstanceProcessor, ProcessInstanceProcessor,
) )
@ -13,20 +11,15 @@ from spiffworkflow_backend.services.script_unit_test_runner import ScriptUnitTes
class TestScriptUnitTestRunner(BaseTest): class TestScriptUnitTestRunner(BaseTest):
"""TestScriptUnitTestRunner."""
def test_takes_data_and_returns_expected_result( def test_takes_data_and_returns_expected_result(
self, self,
app: Flask, app: Flask,
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
"""Test_takes_data_and_returns_expected_result."""
app.config["THREAD_LOCAL_DATA"].process_instance_id = None app.config["THREAD_LOCAL_DATA"].process_instance_id = None
process_group_id = "test_logging_spiff_logger" process_group_id = "test_logging_spiff_logger"
self.create_process_group_with_api(client, with_super_admin_user, process_group_id, process_group_id)
process_model_id = "simple_script" process_model_id = "simple_script"
process_model_identifier = f"{process_group_id}/{process_model_id}" process_model_identifier = f"{process_group_id}/{process_model_id}"
load_test_spec( load_test_spec(
@ -56,14 +49,10 @@ class TestScriptUnitTestRunner(BaseTest):
app: Flask, app: Flask,
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
"""Test_fails_when_expected_output_does_not_match_actual_output."""
app.config["THREAD_LOCAL_DATA"].process_instance_id = None app.config["THREAD_LOCAL_DATA"].process_instance_id = None
process_group_id = "test_logging_spiff_logger" process_group_id = "test_logging_spiff_logger"
self.create_process_group_with_api(client, with_super_admin_user, process_group_id, process_group_id)
process_model_id = "simple_script" process_model_id = "simple_script"
process_model_identifier = f"{process_group_id}/{process_model_id}" process_model_identifier = f"{process_group_id}/{process_model_id}"
load_test_spec( load_test_spec(
@ -93,14 +82,10 @@ class TestScriptUnitTestRunner(BaseTest):
app: Flask, app: Flask,
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
"""Test_script_with_unit_tests_when_hey_is_passed_in."""
app.config["THREAD_LOCAL_DATA"].process_instance_id = None app.config["THREAD_LOCAL_DATA"].process_instance_id = None
process_group_id = "script_with_unit_tests" process_group_id = "script_with_unit_tests"
self.create_process_group_with_api(client, with_super_admin_user, process_group_id, process_group_id)
process_model_id = "script_with_unit_tests" process_model_id = "script_with_unit_tests"
process_model_identifier = f"{process_group_id}/{process_model_id}" process_model_identifier = f"{process_group_id}/{process_model_id}"
load_test_spec( load_test_spec(
@ -126,14 +111,10 @@ class TestScriptUnitTestRunner(BaseTest):
app: Flask, app: Flask,
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
"""Test_script_with_unit_tests_when_hey_is_not_passed_in."""
app.config["THREAD_LOCAL_DATA"].process_instance_id = None app.config["THREAD_LOCAL_DATA"].process_instance_id = None
process_group_id = "script_with_unit_tests" process_group_id = "script_with_unit_tests"
self.create_process_group_with_api(client, with_super_admin_user, process_group_id, process_group_id)
process_model_id = "script_with_unit_tests" process_model_id = "script_with_unit_tests"
process_model_identifier = f"{process_group_id}/{process_model_id}" process_model_identifier = f"{process_group_id}/{process_model_id}"

View File

@ -11,7 +11,6 @@ from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
from spiffworkflow_backend.models.db import db from spiffworkflow_backend.models.db import db
from spiffworkflow_backend.models.spec_reference import SpecReferenceCache from spiffworkflow_backend.models.spec_reference import SpecReferenceCache
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.process_model_service import ProcessModelService from spiffworkflow_backend.services.process_model_service import ProcessModelService
from spiffworkflow_backend.services.spec_file_service import ( from spiffworkflow_backend.services.spec_file_service import (
ProcessModelFileInvalidError, ProcessModelFileInvalidError,
@ -22,8 +21,10 @@ from spiffworkflow_backend.services.spec_file_service import SpecFileService
class TestSpecFileService(BaseTest): class TestSpecFileService(BaseTest):
"""TestSpecFileService.""" """TestSpecFileService."""
process_group_id = "test_process_group_id" process_group_id = ""
process_model_id = "call_activity_nested" process_model_id = "test_process_group_id/call_activity_nested"
# process_group_id = "test_process_group_id"
# process_model_id = "call_activity_nested"
bpmn_file_name = "call_activity_nested.bpmn" bpmn_file_name = "call_activity_nested.bpmn"
call_activity_nested_relative_file_path = os.path.join(process_group_id, process_model_id, bpmn_file_name) call_activity_nested_relative_file_path = os.path.join(process_group_id, process_model_id, bpmn_file_name)
@ -33,16 +34,11 @@ class TestSpecFileService(BaseTest):
app: Flask, app: Flask,
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
"""Test_can_store_process_ids_for_lookup.""" load_test_spec(
self.create_group_and_model_with_bpmn(
client=client,
user=with_super_admin_user,
process_group_id=self.process_group_id,
process_model_id=self.process_model_id, process_model_id=self.process_model_id,
bpmn_file_name=self.bpmn_file_name, bpmn_file_name=self.bpmn_file_name,
bpmn_file_location="call_activity_nested", process_model_source_directory="call_activity_nested",
) )
bpmn_process_id_lookups = SpecReferenceCache.query.all() bpmn_process_id_lookups = SpecReferenceCache.query.all()
assert len(bpmn_process_id_lookups) == 1 assert len(bpmn_process_id_lookups) == 1
@ -54,17 +50,13 @@ class TestSpecFileService(BaseTest):
app: Flask, app: Flask,
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
"""Test_fails_to_save_duplicate_process_id.""" """Test_fails_to_save_duplicate_process_id."""
bpmn_process_identifier = "Level1" bpmn_process_identifier = "Level1"
self.create_group_and_model_with_bpmn( load_test_spec(
client=client,
user=with_super_admin_user,
process_group_id=self.process_group_id,
process_model_id=self.process_model_id, process_model_id=self.process_model_id,
bpmn_file_name=self.bpmn_file_name, bpmn_file_name=self.bpmn_file_name,
bpmn_file_location=self.process_model_id, process_model_source_directory="call_activity_nested",
) )
bpmn_process_id_lookups = SpecReferenceCache.query.all() bpmn_process_id_lookups = SpecReferenceCache.query.all()
assert len(bpmn_process_id_lookups) == 1 assert len(bpmn_process_id_lookups) == 1
@ -87,7 +79,6 @@ class TestSpecFileService(BaseTest):
app: Flask, app: Flask,
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
"""Test_updates_relative_file_path_when_appropriate.""" """Test_updates_relative_file_path_when_appropriate."""
bpmn_process_identifier = "Level1" bpmn_process_identifier = "Level1"
@ -99,13 +90,10 @@ class TestSpecFileService(BaseTest):
db.session.add(process_id_lookup) db.session.add(process_id_lookup)
db.session.commit() db.session.commit()
self.create_group_and_model_with_bpmn( load_test_spec(
client=client,
user=with_super_admin_user,
process_group_id=self.process_group_id,
process_model_id=self.process_model_id, process_model_id=self.process_model_id,
bpmn_file_name=self.bpmn_file_name, bpmn_file_name=self.bpmn_file_name,
bpmn_file_location=self.process_model_id, process_model_source_directory="call_activity_nested",
) )
bpmn_process_id_lookups = SpecReferenceCache.query.all() bpmn_process_id_lookups = SpecReferenceCache.query.all()
@ -139,7 +127,6 @@ class TestSpecFileService(BaseTest):
app: Flask, app: Flask,
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
"""When a BPMN processes identifier is changed in a file, the old id is removed from the cache.""" """When a BPMN processes identifier is changed in a file, the old id is removed from the cache."""
old_identifier = "ye_old_identifier" old_identifier = "ye_old_identifier"
@ -147,19 +134,16 @@ class TestSpecFileService(BaseTest):
identifier=old_identifier, identifier=old_identifier,
relative_path=self.call_activity_nested_relative_file_path, relative_path=self.call_activity_nested_relative_file_path,
file_name=self.bpmn_file_name, file_name=self.bpmn_file_name,
process_model_id=f"{self.process_group_id}/{self.process_model_id}", process_model_id=self.process_model_id,
type="process", type="process",
) )
db.session.add(process_id_lookup) db.session.add(process_id_lookup)
db.session.commit() db.session.commit()
self.create_group_and_model_with_bpmn( load_test_spec(
client=client,
user=with_super_admin_user,
process_group_id=self.process_group_id,
process_model_id=self.process_model_id, process_model_id=self.process_model_id,
bpmn_file_name=self.bpmn_file_name, bpmn_file_name=self.bpmn_file_name,
bpmn_file_location=self.process_model_id, process_model_source_directory="call_activity_nested",
) )
bpmn_process_id_lookups = SpecReferenceCache.query.all() bpmn_process_id_lookups = SpecReferenceCache.query.all()
@ -173,7 +157,6 @@ class TestSpecFileService(BaseTest):
app: Flask, app: Flask,
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
"""Test_load_reference_information. """Test_load_reference_information.
@ -186,32 +169,22 @@ class TestSpecFileService(BaseTest):
a DMN file can (theoretically) contain many decisions. So this a DMN file can (theoretically) contain many decisions. So this
is an array. is an array.
""" """
process_group_id = "test_group" process_model_id = "test_group/call_activity_nested"
process_model_id = "call_activity_nested" process_model = load_test_spec(
process_model_identifier = self.create_group_and_model_with_bpmn(
client=client,
user=with_super_admin_user,
process_group_id=process_group_id,
process_model_id=process_model_id, process_model_id=process_model_id,
# bpmn_file_name=bpmn_file_name, process_model_source_directory="call_activity_nested",
bpmn_file_location=process_model_id,
) )
# load_test_spec( files = SpecFileService.get_files(process_model)
# ,
# process_model_source_directory="call_activity_nested",
# )
process_model_info = ProcessModelService.get_process_model(process_model_identifier)
files = SpecFileService.get_files(process_model_info)
file = next(filter(lambda f: f.name == "call_activity_level_3.bpmn", files)) file = next(filter(lambda f: f.name == "call_activity_level_3.bpmn", files))
ca_3 = SpecFileService.get_references_for_file(file, process_model_info) ca_3 = SpecFileService.get_references_for_file(file, process_model)
assert len(ca_3) == 1 assert len(ca_3) == 1
assert ca_3[0].display_name == "Level 3" assert ca_3[0].display_name == "Level 3"
assert ca_3[0].identifier == "Level3" assert ca_3[0].identifier == "Level3"
assert ca_3[0].type == "process" assert ca_3[0].type == "process"
file = next(filter(lambda f: f.name == "level2c.dmn", files)) file = next(filter(lambda f: f.name == "level2c.dmn", files))
dmn1 = SpecFileService.get_references_for_file(file, process_model_info) dmn1 = SpecFileService.get_references_for_file(file, process_model)
assert len(dmn1) == 1 assert len(dmn1) == 1
assert dmn1[0].display_name == "Decision 1" assert dmn1[0].display_name == "Decision 1"
assert dmn1[0].identifier == "Decision_0vrtcmk" assert dmn1[0].identifier == "Decision_0vrtcmk"

View File

@ -2,34 +2,24 @@
from flask.app import Flask from flask.app import Flask
from flask.testing import FlaskClient from flask.testing import FlaskClient
from tests.spiffworkflow_backend.helpers.base_test import BaseTest from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.process_instance_processor import ( from spiffworkflow_backend.services.process_instance_processor import (
ProcessInstanceProcessor, ProcessInstanceProcessor,
) )
from spiffworkflow_backend.services.process_model_service import ProcessModelService
class TestVariousBpmnConstructs(BaseTest): class TestVariousBpmnConstructs(BaseTest):
"""TestVariousBpmnConstructs."""
def test_running_process_with_timer_intermediate_catch_event( def test_running_process_with_timer_intermediate_catch_event(
self, self,
app: Flask, app: Flask,
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None: ) -> None:
"""Test_running_process_with_timer_intermediate_catch_event.""" process_model = load_test_spec(
process_model_identifier = self.create_group_and_model_with_bpmn( process_model_id="test_group/timer_intermediate_catch_event",
client, process_model_source_directory="timer_intermediate_catch_event",
with_super_admin_user,
"test_group",
"timer_intermediate_catch_event",
) )
process_model = ProcessModelService.get_process_model(process_model_id=process_model_identifier)
process_instance = self.create_process_instance_from_process_model(process_model) process_instance = self.create_process_instance_from_process_model(process_model)
processor = ProcessInstanceProcessor(process_instance) processor = ProcessInstanceProcessor(process_instance)
processor.do_engine_steps(save=True) processor.do_engine_steps(save=True)

View File

@ -847,6 +847,251 @@ describe.only('Initiate a Request - Without Files', () => {
}); });
}); });
// Form validation - Software and License - Without Files
describe('Form validation', () => {
//Special character check
it('Special character check', () => {
const username = Cypress.env('requestor_username');
const password = Cypress.env('requestor_password');
cy.log(`=====username : ${username}`);
cy.log(`=====password : ${password}`);
cy.login(username, password);
cy.visit('/');
cy.contains('Start New +').click();
cy.contains('Request Goods or Services').click();
cy.runPrimaryBpmnFile(true);
/* cy.contains('Please select the type of request to start the process.');
// wait a second to ensure we can click the radio button
cy.wait(2000);
cy.get('input#root-procurement').click();
cy.wait(2000);
cy.get('button')
.contains(/^Submit$/)
.click();
*/
cy.contains('Request Goods or Services', { timeout: 60000 });
cy.url().then((currentUrl) => {
// if url is "/tasks/8/d37c2f0f-016a-4066-b669-e0925b759560"
// extract the digits after /tasks
const processInstanceId = currentUrl.match(/(?<=\/tasks\/)\d+/)[0];
cy.log('==###############===processInstanceId : ', processInstanceId);
const projectId = Cypress.env('project_id');
cy.wait(2000);
cy.get('#root_project').select(projectId);
cy.get('#root_category').select('soft_and_lic');
cy.get('#root_purpose')
.clear()
.type(
'Purpose is to test all the special characters work in the request. ~!@#$%^&*()_+`-= {}[]\ and ;\',./:"<>? end. | this text goes missing', { parseSpecialCharSequences: false }
);
//.type(
// 'Purpose is to test all the special characters work in the request. Test Special chars ~!@#$%^&*()_+`-= {}|[]\ and ;\',./:"<>? end.', { parseSpecialCharSequences: false }
//);
cy.get('#root_criticality').select('High');
cy.get('#root_period').clear().type('25-11-2025');
cy.get('body').click();
cy.get('#root_vendor').clear().type('Microsoft');
cy.get('#root_payment_method').select('Reimbursement');
/* cy.get('button')
.contains(/^Submit$/)
.click();
cy.contains('Task: Enter NDR Items', { timeout: 60000 });
*/
// item 0
cy.get('#root_item_0_sub_category').select('op_src');
cy.get('#root_item_0_item_name')
.clear()
.type(
'Special char test ,./;\'[]\=-0987654321`~!@#$%^&*()_+{}:"<>? end.', { parseSpecialCharSequences: false }
);
//.type(
// 'Special char test ,./;\'[]\=-0987654321`~!@#$%^&*()_+{}|:"<>? end.', { parseSpecialCharSequences: false }
// );
cy.get('#root_item_0_qty').clear().type('2');
cy.get('#root_item_0_currency_type').select('Crypto');
cy.get('#root_item_0_currency').select('SNT');
cy.get('#root_item_0_unit_price').type('1915');
cy.get('#root_item > div:nth-child(3) > p > button').click();
// item 1
cy.get('#root_item_1_sub_category').select('lic_and_sub');
cy.get('#root_item_1_item_name')
.clear()
.type(
'Special char test 2 +_=)(*&^%$#@!~`?></.,":}{\][\';/., <a> {g} [a]end.', { parseSpecialCharSequences: false }
);
//.type(
// 'Special char test 2 +_=)(*&^%$#@!~`?></.,":}{|\][\';/., <a> {g} [a]end.', { parseSpecialCharSequences: false }
//);
cy.get('#root_item_1_qty').clear().type('1');
cy.get('#root_item_1_currency_type').select('Fiat');
cy.get('#root_item_1_currency').select('AED');
cy.get('#root_item_1_unit_price').type('4500');
cy.get('button')
.contains(/^Submit$/)
.click();
cy.contains(
'Review and provide any supporting information or files for your request.',
{ timeout: 60000 }
);
cy.get('.cds--text-area__wrapper')
.find('#root')
.type(
'Test Special chars afs<sfsd>sfsfs,asfdf. sfsf? sfd/sfs f:sfsf " sfsdf; SDFfsd\' sfsdf{sfsfs} sfsdf[ sfsdf] fsfsfd\ sfsd sfsdf=S dfs+ sfd- sfsdf_ sfsfd (sfsd )sfsfsd * sf&sfsfs ^ sfs % sf $ ss# s@ sf! sfd` ss~ END.', { parseSpecialCharSequences: false }
);
//.type(
// 'Test Special chars afs<sfsd>sfsfs,asfdf. sfsf? sfd/sfs f:sfsf " sfsdf; SDFfsd\' sfsdf{sfsfs} sfsdf[ sfsdf] fsfsfd\ sfsd| sfsdf=S dfs+ sfd- sfsdf_ sfsfd (sfsd )sfsfsd * sf&sfsfs ^ sfs % sf $ ss# s@ sf! sfd` ss~ END.', { parseSpecialCharSequences: false }
//);
// cy.contains('Submit the Request').click();
// cy.get('input[value="Submit the Request"]').click();
cy.get('button')
.contains(/^Submit$/)
.click();
cy.get('button')
.contains(/^Return to Home$/)
.click();
cy.contains('Started by me', { timeout: 60000 });
cy.logout();
cy.wait(2000);
});
});
//Check field max lengths
it.only('Check field max lengths', () => {
const username = Cypress.env('requestor_username');
const password = Cypress.env('requestor_password');
cy.log(`=====username : ${username}`);
cy.log(`=====password : ${password}`);
cy.login(username, password);
cy.visit('/');
cy.contains('Start New +').click();
cy.contains('Request Goods or Services').click();
cy.runPrimaryBpmnFile(true);
/* cy.contains('Please select the type of request to start the process.');
// wait a second to ensure we can click the radio button
cy.wait(2000);
cy.get('input#root-procurement').click();
cy.wait(2000);
cy.get('button')
.contains(/^Submit$/)
.click();
*/
cy.contains('Request Goods or Services', { timeout: 60000 });
cy.url().then((currentUrl) => {
// if url is "/tasks/8/d37c2f0f-016a-4066-b669-e0925b759560"
// extract the digits after /tasks
const processInstanceId = currentUrl.match(/(?<=\/tasks\/)\d+/)[0];
cy.log('==###############===processInstanceId : ', processInstanceId);
const projectId = Cypress.env('project_id');
cy.wait(2000);
cy.get('#root_project').select(projectId);
cy.get('#root_category').select('soft_and_lic');
cy.get('#root_purpose')
.clear()
.type(
'Sware\nA software license is a document that provides legally binding guidelines for the use and distribution of software.\nSoftware licenses typically provide end users with the right to one or more copies of the software without violating copyrights. This is now more than 250 characters'
);
cy.get('#root_criticality').select('High');
cy.get('#root_period').clear().type('25-11-2025');
cy.get('body').click();
cy.get('#root_vendor').clear().type('Microsoft');
cy.get('#root_payment_method').select('Reimbursement');
/* cy.get('button')
.contains(/^Submit$/)
.click();
cy.contains('Task: Enter NDR Items', { timeout: 60000 });
*/
// item 0
cy.get('#root_item_0_sub_category').select('op_src');
cy.get('#root_item_0_item_name')
.clear()
.type(
'Open source software is code that is designed to be publicly accessible anyone can see, modify, END. This is now more than 100 characters'
);
cy.get('#root_item_0_qty').clear().type('2');
cy.get('#root_item_0_currency_type').select('Crypto');
cy.get('#root_item_0_currency').select('SNT');
cy.get('#root_item_0_unit_price').type('1915');
cy.get('#root_item > div:nth-child(3) > p > button').click();
// item 1
cy.get('#root_item_1_sub_category').select('lic_and_sub');
cy.get('#root_item_1_item_name')
.clear()
.type(
'A software license is a document that provides legally binding guidelines for the use and distri END.'
);
cy.get('#root_item_1_qty').clear().type('1');
cy.get('#root_item_1_currency_type').select('Fiat');
cy.get('#root_item_1_currency').select('AED');
cy.get('#root_item_1_unit_price').type('4500');
cy.get('button')
.contains(/^Submit$/)
.click();
cy.contains(
'Review and provide any supporting information or files for your request.',
{ timeout: 60000 }
);
cy.get('.cds--text-area__wrapper')
.find('#root')
.type(
'test 2021 Newest HP 17.3 inch FHD Laptop, AMD Ryzen 5 5500U 6core(Beat i7-1160G7, up to 4.0GHz),16GB RAM, 1TB PCIe SSD, Bluetooth 4.2, WiFi, HDMI, USB-A&C, Windows 10 S, w/Ghost Manta Accessories, Silver\nhttps://www.amazon.com/HP-i7-11G7-Bluetooth-Windows'
);
// cy.contains('Submit the Request').click();
// cy.get('input[value="Submit the Request"]').click();
cy.get('button')
.contains(/^Submit$/)
.click();
cy.get('button')
.contains(/^Return to Home$/)
.click();
cy.contains('Started by me', { timeout: 60000 });
cy.logout();
cy.wait(2000);
});
});
});
// Software and License - With Files // Software and License - With Files
describe('Initiate a Request - With Files', () => { describe('Initiate a Request - With Files', () => {
Cypress._.times(1, () => { Cypress._.times(1, () => {

View File

@ -3371,12 +3371,13 @@ describe('Software and Licenses Path - With Files and Multiple items', () => {
cy.get('body').click(); cy.get('body').click();
cy.get('#root_vendor').clear().type('Atlassian'); cy.get('#root_vendor').clear().type('Atlassian');
cy.get('#root_payment_method').select('Debit Card'); cy.get('#root_payment_method').select('Debit Card');
cy.get('button') /*cy.get('button')
.contains(/^Submit$/) .contains(/^Submit$/)
.click(); .click();
cy.contains('Task: Enter NDR Items', { timeout: 60000 });*/
// item 0 // item 0
cy.contains('Task: Enter NDR Items', { timeout: 60000 });
cy.get('#root_item_0_sub_category').select('op_src'); cy.get('#root_item_0_sub_category').select('op_src');
cy.get('#root_item_0_item_name') cy.get('#root_item_0_item_name')
.clear() .clear()

View File

@ -1,4 +1,3 @@
import { string } from 'prop-types';
import { modifyProcessIdentifierForPathParam } from '../../src/helpers'; import { modifyProcessIdentifierForPathParam } from '../../src/helpers';
import { miscDisplayName } from './helpers'; import { miscDisplayName } from './helpers';
import 'cypress-file-upload'; import 'cypress-file-upload';
@ -115,7 +114,10 @@ Cypress.Commands.add(
cy.contains('Task: ', { timeout: 30000 }); cy.contains('Task: ', { timeout: 30000 });
} else { } else {
cy.url().should('include', `/interstitial`); cy.url().should('include', `/interstitial`);
cy.contains('Status: Completed'); // cy.contains('Status: Completed');
cy.contains(
'There are no additional instructions or information for this task.'
);
if (returnToProcessModelShow) { if (returnToProcessModelShow) {
cy.getBySel('process-model-breadcrumb-link').click(); cy.getBySel('process-model-breadcrumb-link').click();
cy.getBySel('process-model-show-permissions-loaded').should('exist'); cy.getBySel('process-model-show-permissions-loaded').should('exist');

View File

@ -18,6 +18,7 @@ export default function ActiveUsers() {
HttpService.makeCallToBackend({ HttpService.makeCallToBackend({
path: `/active-users/updates/${lastVisitedIdentifier}`, path: `/active-users/updates/${lastVisitedIdentifier}`,
successCallback: setActiveUsers, successCallback: setActiveUsers,
httpMethod: 'POST',
}); });
}; };
@ -25,6 +26,7 @@ export default function ActiveUsers() {
HttpService.makeCallToBackend({ HttpService.makeCallToBackend({
path: `/active-users/unregister/${lastVisitedIdentifier}`, path: `/active-users/unregister/${lastVisitedIdentifier}`,
successCallback: setActiveUsers, successCallback: setActiveUsers,
httpMethod: 'POST',
}); });
}; };
updateActiveUsers(); updateActiveUsers();

View File

@ -27,7 +27,7 @@ export const useUriListForPermissions = () => {
processModelCreatePath: `/v1.0/process-models/${params.process_group_id}`, processModelCreatePath: `/v1.0/process-models/${params.process_group_id}`,
processModelFileCreatePath: `/v1.0/process-models/${params.process_model_id}/files`, processModelFileCreatePath: `/v1.0/process-models/${params.process_model_id}/files`,
processModelFileShowPath: `/v1.0/process-models/${params.process_model_id}/files/${params.file_name}`, processModelFileShowPath: `/v1.0/process-models/${params.process_model_id}/files/${params.file_name}`,
processModelPublishPath: `/v1.0/process-models/${params.process_model_id}/publish`, processModelPublishPath: `/v1.0/process-model-publish/${params.process_model_id}`,
processModelShowPath: `/v1.0/process-models/${params.process_model_id}`, processModelShowPath: `/v1.0/process-models/${params.process_model_id}`,
secretListPath: `/v1.0/secrets`, secretListPath: `/v1.0/secrets`,
userSearch: `/v1.0/users/search`, userSearch: `/v1.0/users/search`,

View File

@ -27,7 +27,7 @@ export default function ProcessModelNewExperimental() {
const handleFormSubmission = (event: any) => { const handleFormSubmission = (event: any) => {
event.preventDefault(); event.preventDefault();
HttpService.makeCallToBackend({ HttpService.makeCallToBackend({
path: `/process-models-natural-language/${params.process_group_id}`, path: `/process-model-natural-language/${params.process_group_id}`,
successCallback: navigateToProcessModel, successCallback: navigateToProcessModel,
httpMethod: 'POST', httpMethod: 'POST',
postBody: { natural_language_text: processModelDescriptiveText }, postBody: { natural_language_text: processModelDescriptiveText },

View File

@ -214,7 +214,7 @@ export default function ProcessModelShow() {
setPublishDisabled(true); setPublishDisabled(true);
setProcessModelPublished(null); setProcessModelPublished(null);
HttpService.makeCallToBackend({ HttpService.makeCallToBackend({
path: `/process-models/${modifiedProcessModelId}/publish`, path: targetUris.processModelPublishPath,
successCallback: postPublish, successCallback: postPublish,
httpMethod: 'POST', httpMethod: 'POST',
}); });