diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index 8cf7f1968..000000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,45 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -name: "CodeQL" - -on: - workflow_dispatch: - schedule: - - cron: "0 5 * * *" - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - # Override automatic language detection by changing the below list - # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] - language: ["javascript"] - # Learn more... - # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - - name: Autobuild - uses: github/codeql-action/autobuild@v2 - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml new file mode 100644 index 000000000..83aba6eb2 --- /dev/null +++ b/.github/workflows/cypress.yml @@ -0,0 +1,165 @@ +name: Cypress + +on: + push: + pull_request: + workflow_dispatch: + +# Cancel previous running jobs on the same branch in case of new pushs +concurrency: + group: cypress-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 60 + + strategy: + matrix: + stage: [lite, full, totp] + + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: "16.x" + - name: Install + env: + REF_BRANCH: ${{ github.ref }} + HEAD_REF_BRANCH: ${{ github.head_ref }} + + run: | + python -m pip install --upgrade pip + # version is "x.y.0": convert in x.y + export CURRENT_VERSION=$(grep '"version"' src/package.json | awk {'print $2'} | tr -d '", ' | awk -F. {'print $1 "." $2'}) + pip install --upgrade --no-cache-dir git+https://github.com/rapydo/do.git@${CURRENT_VERSION} + # auth_services=(postgres neo4j) + # AUTH=${auth_services[$(($RANDOM % ${#auth_services[@]}))]} + # echo "Selected random authentication service = ${AUTH}" + AUTH=neo4j + # Make the environment variable available to next steps + echo "AUTH=${AUTH}" >> $GITHUB_ENV + mkdir rapydo_tests + cd rapydo_tests + rapydo install compose + rapydo --testing create prj --auth ${AUTH} --frontend angular -e ENABLE_ANGULAR_MULTI_LANGUAGE=1 --origin-url https://your_remote_git/your_project.git + + # REF contains the branch when commit, but contains refs/pull/XXX/merge on PRs + # with PRs use HEAD_REF + # Strip out refs/heads/ prefix + if [[ ! -z $HEAD_REF_BRANCH ]]; + then + BRANCH=${HEAD_REF_BRANCH/refs\/heads\//} + else + BRANCH=${REF_BRANCH/refs\/heads\//} + fi + # Also strip out tags from the branch + BRANCH=${BRANCH/refs\/tags\/v/} + echo "Forcing rapydo-angular to branch ${BRANCH}" + echo " submodules:" >> projects/prj/project_configuration.yaml + echo " rapydo-angular:" >> projects/prj/project_configuration.yaml + echo " branch: \"${BRANCH}\"" >> projects/prj/project_configuration.yaml + + # This is to create a commit in the history to fill the BUILD variable + git config --global user.email "tests@rapy.do" + git config --global user.name "RAPyDo" + git add -A && git commit -a -m "Initial commit" + + - name: Setup Cypress + uses: rapydo/actions/setup-cypress@v2 + if: ${{ github.event_name != 'pull_request'}} + with: + id: ${{ secrets.CYPRESS_PROJECT_ID }} + key: ${{ secrets.CYPRESS_RECORD_KEY }} + + - name: Setup Project + run: | + cd rapydo_tests + + rapydo --testing init + rapydo add --force component sink + + rapydo pull --quiet + + if [[ "${{ matrix.stage }}" == "full" ]]; then + + rapydo \ + -e API_AUTOSTART=1 \ + -e PROJECT_TITLE="Your ${AUTH} ${{ matrix.stage }} Project" \ + -e ALLOW_TERMS_OF_USE=1 \ + -e AUTH_FORCE_FIRST_PASSWORD_CHANGE=1 \ + -e AUTH_MIN_PASSWORD_LENGTH=10 \ + -e AUTH_MAX_LOGIN_ATTEMPTS=10 \ + -e AUTH_MAX_PASSWORD_VALIDITY=120 \ + -e AUTH_DISABLE_UNUSED_CREDENTIALS_AFTER=60 \ + start + + elif [[ "${{ matrix.stage }}" == "totp" ]]; then + + rapydo \ + -e API_AUTOSTART=1 \ + -e PROJECT_TITLE="Your ${AUTH} ${{ matrix.stage }} Project" \ + -e ALLOW_TERMS_OF_USE=1 \ + -e AUTH_FORCE_FIRST_PASSWORD_CHANGE=1 \ + -e AUTH_MIN_PASSWORD_LENGTH=12 \ + -e AUTH_MAX_LOGIN_ATTEMPTS=10 \ + -e AUTH_MAX_PASSWORD_VALIDITY=120 \ + -e AUTH_DISABLE_UNUSED_CREDENTIALS_AFTER=60 \ + -e AUTH_SECOND_FACTOR_AUTHENTICATION=1 \ + -e AUTH_TOTP_VALIDITY_WINDOW=10 \ + start + + else + + rapydo \ + -e API_AUTOSTART=1 \ + -e PROJECT_TITLE="Your ${AUTH} ${{ matrix.stage }} Project" \ + start + + fi + + sleep 30 + rapydo logs + rapydo shell frontend "yarn install" + rapydo shell frontend "yarn workspaces focus --all" + rapydo shell frontend "reload-types" + rapydo shell frontend "yarn info --recursive --dependents" + rapydo logs + + - name: Run Cypress + run: | + cd rapydo_tests + + if [[ "${{github.actor}}" == "renovate[bot]" ]] || [[ "${{github.event_name}}" == "pull_request" ]]; then + rapydo shell frontend "yarn run cypress:start:norecord" + else + rapydo shell frontend "yarn run cypress:start:${{ matrix.stage }}" + fi + + rapydo shell frontend "npx nyc --all report --reporter=lcov --report-dir /coverage" + + - name: Coverage + uses: rapydo/actions/coverage@v2 + with: + repository: rapydo_tests/submodules/rapydo-angular + cov_file: rapydo_tests/data/prj/karma/lcov.info + + - name: Print backend logs on failure + if: ${{ failure() }} + run: cat rapydo_tests/data/logs/backend-server.log + + - name: Print mocked email on failure + if: ${{ failure() }} + run: cat rapydo_tests/data/logs/mock.mail.*.body 2>/dev/null || true + + - name: Docker logs on failure + if: failure() + run: | + cd rapydo_tests + rapydo logs diff --git a/.github/workflows/karma.yml b/.github/workflows/karma.yml new file mode 100644 index 000000000..4c3493171 --- /dev/null +++ b/.github/workflows/karma.yml @@ -0,0 +1,87 @@ +name: Karma + +on: + push: + pull_request: + workflow_dispatch: + +# Cancel previous running jobs on the same branch in case of new pushs +concurrency: + group: karma-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: "16.x" + - name: Install + env: + REF_BRANCH: ${{ github.ref }} + HEAD_REF_BRANCH: ${{ github.head_ref }} + + run: | + python -m pip install --upgrade pip + # version is "x.y.0": convert in x.y + export CURRENT_VERSION=$(grep '"version"' src/package.json | awk {'print $2'} | tr -d '", ' | awk -F. {'print $1 "." $2'}) + pip install --upgrade --no-cache-dir git+https://github.com/rapydo/do.git@${CURRENT_VERSION} + mkdir rapydo_tests + cd rapydo_tests + rapydo install compose + rapydo --testing create prj --auth neo4j --frontend angular --origin-url https://your_remote_git/your_project.git + + # REF contains the branch when commit, but contains refs/pull/XXX/merge on PRs + # with PRs use HEAD_REF + # Strip out refs/heads/ prefix + if [[ ! -z $HEAD_REF_BRANCH ]]; + then + BRANCH=${HEAD_REF_BRANCH/refs\/heads\//} + else + BRANCH=${REF_BRANCH/refs\/heads\//} + fi + # Also strip out tags from the branch + BRANCH=${BRANCH/refs\/tags\/v/} + echo "Forcing rapydo-angular to branch ${BRANCH}" + echo " submodules:" >> projects/prj/project_configuration.yaml + echo " rapydo-angular:" >> projects/prj/project_configuration.yaml + echo " branch: \"${BRANCH}\"" >> projects/prj/project_configuration.yaml + + # This is to create a commit in the history to fill the BUILD variable + git config --global user.email "tests@rapy.do" + git config --global user.name "RAPyDo" + git add -A && git commit -a -m "Initial commit" + + - name: Run Karma + run: | + cd rapydo_tests + rapydo --testing init + rapydo pull --quiet frontend + rapydo start frontend + sleep 5 + rapydo shell frontend "yarn install" + rapydo shell frontend "yarn workspaces focus --all" + rapydo shell frontend "reload-types" + rapydo shell frontend "yarn info --recursive --dependents" + rapydo shell frontend "yarn run test:single" + + - name: Coverage + uses: rapydo/actions/coverage@v2 + with: + repository: rapydo_tests/submodules/rapydo-angular + cov_file: rapydo_tests/data/prj/karma/lcov.info + + - name: Docker logs on failure + if: failure() + run: | + cd rapydo_tests + rapydo logs diff --git a/.github/workflows/ossar-analysis.yml b/.github/workflows/ossar-analysis.yml deleted file mode 100644 index 37701687b..000000000 --- a/.github/workflows/ossar-analysis.yml +++ /dev/null @@ -1,39 +0,0 @@ -# This workflow integrates a collection of open source static analysis tools -# with GitHub code scanning. For documentation, or to provide feedback, visit -# https://github.com/github/ossar-action -name: OSSAR - -on: - workflow_dispatch: - schedule: - - cron: "0 5 * * *" - -jobs: - OSSAR-Scan: - # OSSAR runs on windows-latest. - # ubuntu-latest and macos-latest support coming soon - runs-on: windows-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - # Ensure a compatible version of dotnet is installed. - # The [Microsoft Security Code Analysis CLI](https://aka.ms/mscadocs) is built with dotnet v3.1.201. - # A version greater than or equal to v3.1.201 of dotnet must be installed on the agent in order to run this action. - # GitHub hosted runners already have a compatible version of dotnet installed and this step may be skipped. - # For self-hosted runners, ensure dotnet version 3.1.201 or later is installed by including this action: - # - name: Install .NET - # uses: actions/setup-dotnet@v1 - # with: - # dotnet-version: '3.1.x' - # Run open source static analysis tools - - name: Run OSSAR - uses: github/ossar-action@v1 - id: ossar - - # Upload results to the Security tab - - name: Upload OSSAR results - uses: github/codeql-action/upload-sarif@v2 - with: - sarif_file: ${{ steps.ossar.outputs.sarifFile }} diff --git a/.github/workflows/tests.yml b/.github/workflows/prod.yml similarity index 66% rename from .github/workflows/tests.yml rename to .github/workflows/prod.yml index c34271672..71b20bf5f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/prod.yml @@ -1,4 +1,4 @@ -name: Test +name: Prod on: push: @@ -7,7 +7,7 @@ on: # Cancel previous running jobs on the same branch in case of new pushs concurrency: - group: tests-${{ github.ref }} + group: prod-${{ github.ref }} cancel-in-progress: true jobs: @@ -15,26 +15,14 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 - strategy: - matrix: - stage: - [ - karma, - cypress-lite, - cypress-full, - cypress-totp, - cypress-hiddenlogin, - prod, - ] - steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.11" - name: Set up Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: "16.x" - name: Install @@ -47,15 +35,10 @@ jobs: # version is "x.y.0": convert in x.y export CURRENT_VERSION=$(grep '"version"' src/package.json | awk {'print $2'} | tr -d '", ' | awk -F. {'print $1 "." $2'}) pip install --upgrade --no-cache-dir git+https://github.com/rapydo/do.git@${CURRENT_VERSION} - auth_services=(postgres neo4j) - AUTH=${auth_services[$(($RANDOM % ${#auth_services[@]}))]} - echo "Selected random authentication service = ${AUTH}" - # Make the environment variable available to next steps - echo "AUTH=${AUTH}" >> $GITHUB_ENV mkdir rapydo_tests cd rapydo_tests rapydo install compose - rapydo --testing create prj --auth ${AUTH} --frontend angular --origin-url https://your_remote_git/your_project.git + rapydo --testing create prj --auth no --frontend angular --origin-url https://your_remote_git/your_project.git # REF contains the branch when commit, but contains refs/pull/XXX/merge on PRs # with PRs use HEAD_REF @@ -69,116 +52,16 @@ jobs: # Also strip out tags from the branch BRANCH=${BRANCH/refs\/tags\/v/} echo "Forcing rapydo-angular to branch ${BRANCH}" - sed -i "s|# branch: \"rapydo-angular-branch\"|branch: \"${BRANCH}\"|g" projects/prj/project_configuration.yaml + echo " submodules:" >> projects/prj/project_configuration.yaml + echo " rapydo-angular:" >> projects/prj/project_configuration.yaml + echo " branch: \"${BRANCH}\"" >> projects/prj/project_configuration.yaml # This is to create a commit in the history to fill the BUILD variable git config --global user.email "tests@rapy.do" git config --global user.name "RAPyDo" git add -A && git commit -a -m "Initial commit" - - name: Setup Cypress - uses: rapydo/actions/setup-cypress@v2 - if: ${{ matrix.stage != 'karma' && matrix.stage != 'prod' && github.event_name != 'pull_request'}} - with: - id: ${{ secrets.CYPRESS_PROJECT_ID }} - key: ${{ secrets.CYPRESS_RECORD_KEY }} - - - name: Setup Project - if: ${{ matrix.stage != 'karma' && matrix.stage != 'prod' }} - run: | - cd rapydo_tests - - rapydo --testing init - rapydo add --force component sink - - rapydo pull --quiet - - STAGE="${{ matrix.stage }}" - LABEL=${STAGE/cypress-/} - - if [[ "${LABEL}" == "full" ]]; then - - rapydo \ - -e API_AUTOSTART=1 \ - -e PROJECT_TITLE="Your ${AUTH} ${LABEL} Project" \ - -e ALLOW_TERMS_OF_USE=1 \ - -e AUTH_FORCE_FIRST_PASSWORD_CHANGE=1 \ - -e AUTH_MIN_PASSWORD_LENGTH=10 \ - -e AUTH_MAX_LOGIN_ATTEMPTS=10 \ - -e AUTH_MAX_PASSWORD_VALIDITY=120 \ - -e AUTH_DISABLE_UNUSED_CREDENTIALS_AFTER=60 \ - start - - elif [[ "${LABEL}" == "totp" ]]; then - - rapydo \ - -e API_AUTOSTART=1 \ - -e PROJECT_TITLE="Your ${AUTH} ${LABEL} Project" \ - -e ALLOW_TERMS_OF_USE=1 \ - -e AUTH_FORCE_FIRST_PASSWORD_CHANGE=1 \ - -e AUTH_MIN_PASSWORD_LENGTH=12 \ - -e AUTH_MAX_LOGIN_ATTEMPTS=10 \ - -e AUTH_MAX_PASSWORD_VALIDITY=120 \ - -e AUTH_DISABLE_UNUSED_CREDENTIALS_AFTER=60 \ - -e AUTH_SECOND_FACTOR_AUTHENTICATION=1 \ - -e AUTH_TOTP_VALIDITY_WINDOW=10 \ - start - - elif [[ "${LABEL}" == "hiddenlogin" ]]; then - - rapydo \ - -e API_AUTOSTART=1 \ - -e PROJECT_TITLE="Your ${AUTH} ${LABEL} Project" \ - -e SHOW_LOGIN=0 \ - start - - else - - rapydo \ - -e API_AUTOSTART=1 \ - -e PROJECT_TITLE="Your ${AUTH} ${LABEL} Project" \ - start - - fi - - sleep 30 - rapydo logs - rapydo shell frontend "yarn install" - rapydo shell frontend "yarn workspaces focus --all" - rapydo shell frontend "reload-types" - rapydo logs - - - name: Run Karma - if: ${{ matrix.stage == 'karma' }} - run: | - cd rapydo_tests - rapydo --testing init - rapydo pull --quiet frontend - rapydo start frontend - sleep 5 - rapydo shell frontend "yarn install" - rapydo shell frontend "yarn workspaces focus --all" - rapydo shell frontend "reload-types" - rapydo shell frontend "yarn run test:single" - - - name: Run Cypress - if: ${{ matrix.stage != 'karma' && matrix.stage != 'prod' }} - run: | - cd rapydo_tests - - STAGE="${{ matrix.stage }}" - LABEL=${STAGE/cypress-/} - - if [[ "${{github.actor}}" == "dependabot[bot]" ]] || [[ "${{github.event_name}}" == "pull_request" ]]; then - rapydo shell frontend "yarn run cypress:start:norecord" - else - rapydo shell frontend "yarn run cypress:start:${LABEL}" - fi - - rapydo shell frontend "npx nyc --all report --reporter=lcov --report-dir /coverage" - - name: Run Production Tests - if: ${{ matrix.stage == 'prod' }} run: | cd rapydo_tests rapydo --testing --prod init @@ -308,21 +191,6 @@ jobs: rapydo logs --tail 6 frontend 2>&1 | grep "files have been compressed." curl --insecure -X GET https://localhost - - name: Coverage - uses: rapydo/actions/coverage@v2 - if: ${{ matrix.stage != 'prod' }} - with: - repository: rapydo_tests/submodules/rapydo-angular - cov_file: rapydo_tests/data/prj/karma/lcov.info - - - name: Print backend logs on failure - if: ${{ failure() && matrix.stage != 'karma' && matrix.stage != 'prod' }} - run: cat rapydo_tests/data/logs/backend-server.log - - - name: Print mocked email on failure - if: ${{ failure() && matrix.stage != 'karma' && matrix.stage != 'prod' }} - run: cat rapydo_tests/data/logs/mock.mail.*.body 2>/dev/null || true - - name: Docker logs on failure if: failure() run: | diff --git a/.github/workflows/semgrep-analysis.yml b/.github/workflows/semgrep-analysis.yml deleted file mode 100644 index 63a086254..000000000 --- a/.github/workflows/semgrep-analysis.yml +++ /dev/null @@ -1,32 +0,0 @@ -# This workflow file requires a free account on Semgrep.dev to -# manage rules, file ignores, notifications, and more. -# -# See https://semgrep.dev/docs - -name: Semgrep - -on: - workflow_dispatch: - schedule: - - cron: "0 5 * * *" - -jobs: - semgrep: - name: Scan - runs-on: ubuntu-latest - steps: - # Checkout project source - - uses: actions/checkout@v3 - - # Scan code using project's configuration on https://semgrep.dev/manage - - uses: returntocorp/semgrep-action@v1 - with: - publishToken: ${{ secrets.SEMGREP_APP_TOKEN }} - generateSarif: "1" - - # Upload SARIF file generated in previous step - - name: Upload SARIF file - uses: github/codeql-action/upload-sarif@v2 - with: - sarif_file: semgrep.sarif - if: always() diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml new file mode 100644 index 000000000..e1629823e --- /dev/null +++ b/.github/workflows/static-analysis.yml @@ -0,0 +1,83 @@ +name: "Static Analysis" + +on: + workflow_dispatch: + schedule: + - cron: "0 5 * * *" + +jobs: + codeql: + name: CodeQL + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: "javascript" + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + + ossar: + name: OSSAR + # OSSAR runs on windows-latest. + # ubuntu-latest and macos-latest support coming soon + runs-on: windows-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Ensure a compatible version of dotnet is installed. + # The [Microsoft Security Code Analysis CLI](https://aka.ms/mscadocs) is built with dotnet v3.1.201. + # A version greater than or equal to v3.1.201 of dotnet must be installed on the agent in order to run this action. + # GitHub hosted runners already have a compatible version of dotnet installed and this step may be skipped. + # For self-hosted runners, ensure dotnet version 3.1.201 or later is installed by including this action: + # - name: Install .NET + # uses: actions/setup-dotnet@v1 + # with: + # dotnet-version: '3.1.x' + # Run open source static analysis tools + - name: Run OSSAR + uses: github/ossar-action@v1 + id: ossar + + # Upload results to the Security tab + - name: Upload OSSAR results + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: ${{ steps.ossar.outputs.sarifFile }} + + semgrep: + name: Semgrep + runs-on: ubuntu-latest + steps: + # Checkout project source + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.10" + - name: Install Semgrep + run: python3 -m pip install semgrep + - name: Run Semgrep + env: + SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} + run: semgrep ci --sarif > semgrep.sarif + + # Upload SARIF file generated in previous step + - name: Upload SARIF file + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: semgrep.sarif + if: always() diff --git a/.gitignore b/.gitignore index 445bbbc1e..a5108b13c 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,6 @@ coverage.xml # GitGuardian .cache_ggshield + +# Ignore compiled i18n translations +src/assets/i18n diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e36d953d1..cd8ecd611 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,11 +1,5 @@ repos: - repo: https://github.com/pre-commit/mirrors-prettier - rev: v2.7.1 + rev: v3.1.0 hooks: - id: prettier - - repo: https://github.com/gitguardian/gg-shield - rev: v1.13.6 - hooks: - - id: ggshield - language_version: python3 - stages: [commit] diff --git a/README.md b/README.md index 1bb3176d3..3a3736878 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Based on: - ngx-uploadx - file-saver(es) - ngx-toastr -- moment + ngx-moment +- date-fns + ngx-date-fns - font-awesome - ngx-clipboard - angular2-cookie-law diff --git a/cypress.config.ts b/cypress.config.ts index c43d24828..6ca1295b6 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -4,10 +4,12 @@ export default defineConfig({ e2e: { baseUrl: "http://localhost:8080", watchForFileChanges: false, - specPattern: "/app/cypress/e2e/**/*.ts", + specPattern: "/e2e/**/*.ts", screenshotsFolder: "/cypress", + video: true, videosFolder: "/cypress", downloadsFolder: "/cypress", + supportFile: "/e2e/support/e2e.js", retries: { openMode: 0, runMode: 0, diff --git a/cypress/e2e/404.spec.ts b/cypress/e2e/404.spec.ts index fd00a8143..aef928214 100644 --- a/cypress/e2e/404.spec.ts +++ b/cypress/e2e/404.spec.ts @@ -13,10 +13,10 @@ describe("404", () => { cy.get("div.card-header h1").contains("404 - Page not found"); cy.get("div.card-body h2").contains( - "The page you requested could not be found!" + "The page you requested could not be found!", ); cy.get("div.card-body h4").contains( - "Please report the error if you reached this page by following a link" + "Please report the error if you reached this page by following a link", ); cy.contains("GO HOME"); diff --git a/cypress/e2e/aaa_init_default_user.spec.ts b/cypress/e2e/aaa_init_default_user.spec.ts index ad5480c8d..cf6deca3c 100644 --- a/cypress/e2e/aaa_init_default_user.spec.ts +++ b/cypress/e2e/aaa_init_default_user.spec.ts @@ -1,7 +1,7 @@ // This is to silence ESLint about undefined cy /*global cy, Cypress*/ -import { getpassword, get_totp } from "../../fixtures/utilities"; +import { getpassword, get_totp } from "../fixtures/utilities"; describe("Init user", () => { it("Login via form to change first password / setup TOTP if needed", () => { @@ -34,7 +34,7 @@ describe("Init user", () => { .clear() .type(pwd, { parseSpecialCharSequences: false }); cy.get( - 'input[placeholder="Type again the new password for confirmation"]' + 'input[placeholder="Type again the new password for confirmation"]', ) .clear() .type(pwd, { parseSpecialCharSequences: false }); @@ -46,6 +46,9 @@ describe("Init user", () => { } cy.get("button:contains('Submit')").click(); + cy.location().should((location) => { + expect(location.pathname).to.not.eq("/app/profile/changepassword"); + }); } if (Cypress.env("AUTH_DISABLE_UNUSED_CREDENTIALS_AFTER")) { diff --git a/cypress/e2e/admin_groups.spec.ts b/cypress/e2e/admin_groups.spec.ts index b05ff8aaa..1ec52b71a 100644 --- a/cypress/e2e/admin_groups.spec.ts +++ b/cypress/e2e/admin_groups.spec.ts @@ -49,20 +49,20 @@ describe("AdminGroups", () => { cy.get("datatable-body").contains( "datatable-body-cell", - "_TestGroup-" + randval + "_TestGroup-" + randval, ); // Test duplications cy.get('button:contains("new group")').click({ force: true }); cy.get('input[placeholder="Short name"]').type("_TestGroup-" + randval); cy.get('input[placeholder="Full name"]').type( - "Long name for test Group " + randval + "Long name for test Group " + randval, ); cy.get("@submit").click({ force: true }); cy.checkalert( - "A Group already exists with shortname: _TestGroup-" + randval + "A Group already exists with shortname: _TestGroup-" + randval, ); cy.get('button:contains("Close")').click({ force: true }); diff --git a/cypress/e2e/admin_groups_staff.spec.ts b/cypress/e2e/admin_groups_staff.spec.ts index 607da8ad9..54495781f 100644 --- a/cypress/e2e/admin_groups_staff.spec.ts +++ b/cypress/e2e/admin_groups_staff.spec.ts @@ -1,6 +1,6 @@ // This is to silence ESLint about undefined cy /*global cy, Cypress*/ -import { getpassword, get_random_username } from "../../fixtures/utilities"; +import { getpassword, get_random_username } from "../fixtures/utilities"; /* mostly copied From AdminUsers */ @@ -64,20 +64,20 @@ if (Cypress.env("AUTH_ROLES").includes(",staff_user,")) { cy.get("datatable-body").contains( "datatable-body-cell", - "_TestGroup-" + randval + "_TestGroup-" + randval, ); // Test duplications cy.get('button:contains("new group")').click({ force: true }); cy.get('input[placeholder="Short name"]').type("_TestGroup-" + randval); cy.get('input[placeholder="Full name"]').type( - "Long name for test Group " + randval + "Long name for test Group " + randval, ); cy.get("@submit").click({ force: true }); cy.checkalert( - "A Group already exists with shortname: _TestGroup-" + randval + "A Group already exists with shortname: _TestGroup-" + randval, ); cy.get('button:contains("Close")').click({ force: true }); diff --git a/cypress/e2e/admin_logins.spec.ts b/cypress/e2e/admin_logins.spec.ts index 7f1bf9e26..40422dd68 100644 --- a/cypress/e2e/admin_logins.spec.ts +++ b/cypress/e2e/admin_logins.spec.ts @@ -1,6 +1,6 @@ // This is to silence ESLint about undefined cy /*global cy, Cypress*/ -import { getpassword, get_random_username } from "../../fixtures/utilities"; +import { getpassword, get_random_username } from "../fixtures/utilities"; describe("AdminLogins", () => { const random_username = get_random_username("random"); diff --git a/cypress/e2e/admin_mail.spec.ts b/cypress/e2e/admin_mail.spec.ts index e32293233..2dbb16a4f 100644 --- a/cypress/e2e/admin_mail.spec.ts +++ b/cypress/e2e/admin_mail.spec.ts @@ -26,8 +26,7 @@ describe("AdminMail", () => { cy.get('input[placeholder="Subject of your email"]') .clear() .type("Your subject"); - // It should work because there is only 1 textarea - cy.get("textarea").clear().type("Your body!"); + cy.get("textarea").first().clear().type("Your body!"); cy.get('input[placeholder="Destination email address"]') .clear() .type("Your email"); @@ -45,7 +44,7 @@ describe("AdminMail", () => { .click({ force: true }); cy.get("div.modal-header h1.modal-title").contains( - "Do you want to send the following email?" + "Do you want to send the following email?", ); cy.get("div.modal-body").contains("Subject: Your subject"); cy.get("div.modal-body").contains("To: sample@nomail.org"); @@ -72,7 +71,7 @@ describe("AdminMail", () => { cy.checkalert("Mail successfully sent"); cy.get("div.card-body").contains( - "Your email with subject: Your subject has been successfully sent!" + "Your email with subject: Your subject has been successfully sent!", ); cy.get("div.card-body").contains("Destination address: sample@nomail.org"); @@ -84,16 +83,15 @@ describe("AdminMail", () => { cy.get('input[placeholder="Subject of your email"]') .clear() .type("Your subject"); - // It should work because there is only 1 textarea - cy.get("textarea").clear().type("Your body!"); + cy.get("textarea").first().clear().type("Your body!"); cy.get('input[placeholder="Destination email address"]') .clear() .type("sample@nomail.org"); - cy.get('input[placeholder="CC email addresses (comma-delimited list)"]') + cy.get('textarea[placeholder="CC email addresses (comma-delimited list)"]') .clear() .type("Your email"); - cy.get('input[placeholder="BCC email addresses (comma-delimited list)"]') + cy.get('textarea[placeholder="BCC email addresses (comma-delimited list)"]') .clear() .type("Your email"); @@ -102,15 +100,11 @@ describe("AdminMail", () => { .click({ force: true }); cy.checkalert("Not a valid email address."); - // Without this wait the click will happen on the same popup... - // let's wait the first to disappear before clicking on the ther - cy.wait(300); - cy.checkalert("Not a valid email address."); - cy.get('input[placeholder="CC email addresses (comma-delimited list)"]') + cy.get('textarea[placeholder="CC email addresses (comma-delimited list)"]') .clear() .type("sample1@nomail.org,sample2"); - cy.get('input[placeholder="BCC email addresses (comma-delimited list)"]') + cy.get('textarea[placeholder="BCC email addresses (comma-delimited list)"]') .clear() .type("sample3@nomail.org,sample4"); @@ -119,15 +113,11 @@ describe("AdminMail", () => { .click({ force: true }); cy.checkalert("Not a valid email address."); - // Without this wait the click will happen on the same popup... - // let's wait the first to disappear before clicking on the ther - cy.wait(300); - cy.checkalert("Not a valid email address."); - cy.get('input[placeholder="CC email addresses (comma-delimited list)"]') + cy.get('textarea[placeholder="CC email addresses (comma-delimited list)"]') .clear() .type("sample1@nomail.org,sample2@nomail.org"); - cy.get('input[placeholder="BCC email addresses (comma-delimited list)"]') + cy.get('textarea[placeholder="BCC email addresses (comma-delimited list)"]') .clear() .type("sample3@nomail.org,sample4@nomail.org"); @@ -136,15 +126,15 @@ describe("AdminMail", () => { .click({ force: true }); cy.get("div.modal-header h1.modal-title").contains( - "Do you want to send the following email?" + "Do you want to send the following email?", ); cy.get("div.modal-body").contains("Subject: Your subject"); cy.get("div.modal-body").contains("To: sample@nomail.org"); cy.get("div.modal-body").contains( - "CC: sample1@nomail.org,sample2@nomail.org" + "CC: sample1@nomail.org,sample2@nomail.org", ); cy.get("div.modal-body").contains( - "BCC: sample3@nomail.org,sample4@nomail.org" + "BCC: sample3@nomail.org,sample4@nomail.org", ); cy.get("ul.nav-tabs li.nav-item a").contains("Plain Body").click(); @@ -169,14 +159,14 @@ describe("AdminMail", () => { cy.checkalert("Mail successfully sent"); cy.get("div.card-body").contains( - "Your email with subject: Your subject has been successfully sent!" + "Your email with subject: Your subject has been successfully sent!", ); cy.get("div.card-body").contains("Destination address: sample@nomail.org"); cy.get("div.card-body").contains( - "CC: sample1@nomail.org,sample2@nomail.org" + "CC: sample1@nomail.org,sample2@nomail.org", ); cy.get("div.card-body").contains( - "BCC: sample3@nomail.org,sample4@nomail.org" + "BCC: sample3@nomail.org,sample4@nomail.org", ); }); }); diff --git a/cypress/e2e/admin_sessions.spec.ts b/cypress/e2e/admin_sessions.spec.ts index 9166a8f11..3dae716a8 100644 --- a/cypress/e2e/admin_sessions.spec.ts +++ b/cypress/e2e/admin_sessions.spec.ts @@ -83,7 +83,7 @@ describe("AdminSessions", () => { // cy.checkalert("Token successfully copied"); // Clipboard verification requires an additional plugin... - } + }, ); // This is the same as in profile.sessions.spec @@ -107,7 +107,7 @@ describe("AdminSessions", () => { cy.get("button").contains("Yes, delete").click(); cy.checkalert("Confirmation: token successfully deleted"); - } + }, ); it("Backend errors", () => { diff --git a/cypress/e2e/admin_users.spec.ts b/cypress/e2e/admin_users.spec.ts index 9e09e1712..3353af4b0 100644 --- a/cypress/e2e/admin_users.spec.ts +++ b/cypress/e2e/admin_users.spec.ts @@ -2,7 +2,7 @@ /*global cy, Cypress*/ /* mostly copied in StaffUsers */ -import { getpassword, get_random_username } from "../../fixtures/utilities"; +import { getpassword, get_random_username } from "../fixtures/utilities"; describe("AdminUsers", () => { // do not directly create the random values here, @@ -57,7 +57,7 @@ describe("AdminUsers", () => { 2, "Should have at least " + Cypress.env("AUTH_MIN_PASSWORD_LENGTH") + - " characters" + " characters", ); cy.contains("random").click({ force: true }); @@ -92,7 +92,7 @@ describe("AdminUsers", () => { cy.checkalert( "A User already exists with email: " + - Cypress.env("AUTH_DEFAULT_USERNAME") + Cypress.env("AUTH_DEFAULT_USERNAME"), ); cy.get("@email").clear().type(username); @@ -231,6 +231,11 @@ describe("AdminUsers", () => { cy.get("datatable-body-row").its("length").should("be.gte", 1); }); + it("Download users as excel", () => { + cy.get("div.card-header div i.fa-download").click(); + // TODO: to be implemented. See profile.sessions.spec.ts + }); + it("Backend errors", () => { cy.intercept("DELETE", /\/api\/admin\/users\/*/, { statusCode: 500, diff --git a/cypress/e2e/admin_users_staff.spec.ts b/cypress/e2e/admin_users_staff.spec.ts index 779191182..aeacaaa14 100644 --- a/cypress/e2e/admin_users_staff.spec.ts +++ b/cypress/e2e/admin_users_staff.spec.ts @@ -2,7 +2,7 @@ /*global cy, Cypress*/ /* mostly copied From AdminUsers */ -import { getpassword, get_random_username } from "../../fixtures/utilities"; +import { getpassword, get_random_username } from "../fixtures/utilities"; if (Cypress.env("AUTH_ROLES").includes(",staff_user,")) { describe("StaffUsers", () => { @@ -67,7 +67,7 @@ if (Cypress.env("AUTH_ROLES").includes(",staff_user,")) { 2, "Should have at least " + Cypress.env("AUTH_MIN_PASSWORD_LENGTH") + - " characters" + " characters", ); cy.get("@password") diff --git a/cypress/e2e/authorizations.spec.ts b/cypress/e2e/authorizations.spec.ts index 5439f64c6..d3ad7a536 100644 --- a/cypress/e2e/authorizations.spec.ts +++ b/cypress/e2e/authorizations.spec.ts @@ -1,6 +1,6 @@ // This is to silence ESLint about undefined cy /*global cy, Cypress*/ -import { getpassword, get_random_username } from "../../fixtures/utilities"; +import { getpassword, get_random_username } from "../fixtures/utilities"; describe("Test Authorizations", () => { it("Test Admin authorizations", () => { @@ -65,13 +65,11 @@ describe("Test Authorizations", () => { expect(location.pathname).to.not.eq("/app/group/users"); }); cy.checkalert( - "Permission denied: you are not authorized to access this page" + "Permission denied: you are not authorized to access this page", ); - // cy.login(email, pwd); //////////////////////////////////////////////////////////////////// // Delete temporary user - // cy.logout(); cy.login(); cy.deleteuser(email); }); @@ -120,7 +118,7 @@ describe("Test Authorizations", () => { expect(location.pathname).to.not.eq("/app/admin/sessions"); }); cy.checkalert( - "Permission denied: you are not authorized to access this page" + "Permission denied: you are not authorized to access this page", ); cy.login(email, pwd); //////////////////////////////////////////////////////////////////// @@ -129,7 +127,7 @@ describe("Test Authorizations", () => { expect(location.pathname).to.not.eq("/app/admin/stats"); }); cy.checkalert( - "Permission denied: you are not authorized to access this page" + "Permission denied: you are not authorized to access this page", ); cy.login(email, pwd); //////////////////////////////////////////////////////////////////// @@ -138,7 +136,7 @@ describe("Test Authorizations", () => { expect(location.pathname).to.not.eq("/app/admin/mail"); }); cy.checkalert( - "Permission denied: you are not authorized to access this page" + "Permission denied: you are not authorized to access this page", ); cy.login(email, pwd); //////////////////////////////////////////////////////////////////// @@ -147,13 +145,11 @@ describe("Test Authorizations", () => { expect(location.pathname).to.not.eq("/app/group/users"); }); cy.checkalert( - "Permission denied: you are not authorized to access this page" + "Permission denied: you are not authorized to access this page", ); - // cy.login(email, pwd); //////////////////////////////////////////////////////////////////// // Delete temporary user - // cy.logout(); cy.login(); cy.deleteuser(email); }); @@ -190,7 +186,7 @@ describe("Test Authorizations", () => { expect(location.pathname).to.not.eq("/app/admin/users"); }); cy.checkalert( - "Permission denied: you are not authorized to access this page" + "Permission denied: you are not authorized to access this page", ); cy.login(email, pwd); //////////////////////////////////////////////////////////////////// @@ -199,7 +195,7 @@ describe("Test Authorizations", () => { expect(location.pathname).to.not.eq("/app/admin/groups"); }); cy.checkalert( - "Permission denied: you are not authorized to access this page" + "Permission denied: you are not authorized to access this page", ); cy.login(email, pwd); //////////////////////////////////////////////////////////////////// @@ -208,7 +204,7 @@ describe("Test Authorizations", () => { expect(location.pathname).to.not.eq("/app/admin/sessions"); }); cy.checkalert( - "Permission denied: you are not authorized to access this page" + "Permission denied: you are not authorized to access this page", ); cy.login(email, pwd); //////////////////////////////////////////////////////////////////// @@ -217,7 +213,7 @@ describe("Test Authorizations", () => { expect(location.pathname).to.not.eq("/app/admin/stats"); }); cy.checkalert( - "Permission denied: you are not authorized to access this page" + "Permission denied: you are not authorized to access this page", ); cy.login(email, pwd); //////////////////////////////////////////////////////////////////// @@ -226,7 +222,7 @@ describe("Test Authorizations", () => { expect(location.pathname).to.not.eq("/app/admin/mail"); }); cy.checkalert( - "Permission denied: you are not authorized to access this page" + "Permission denied: you are not authorized to access this page", ); cy.login(email, pwd); //////////////////////////////////////////////////////////////////// @@ -238,7 +234,6 @@ describe("Test Authorizations", () => { //////////////////////////////////////////////////////////////////// // Delete temporary user - cy.logout(); cy.login(); cy.deleteuser(email); }); @@ -274,7 +269,7 @@ describe("Test Authorizations", () => { expect(location.pathname).to.not.eq("/app/admin/users"); }); cy.checkalert( - "Permission denied: you are not authorized to access this page" + "Permission denied: you are not authorized to access this page", ); cy.login(email, pwd); //////////////////////////////////////////////////////////////////// @@ -283,7 +278,7 @@ describe("Test Authorizations", () => { expect(location.pathname).to.not.eq("/app/admin/groups"); }); cy.checkalert( - "Permission denied: you are not authorized to access this page" + "Permission denied: you are not authorized to access this page", ); cy.login(email, pwd); //////////////////////////////////////////////////////////////////// @@ -292,7 +287,7 @@ describe("Test Authorizations", () => { expect(location.pathname).to.not.eq("/app/admin/sessions"); }); cy.checkalert( - "Permission denied: you are not authorized to access this page" + "Permission denied: you are not authorized to access this page", ); cy.login(email, pwd); //////////////////////////////////////////////////////////////////// @@ -301,7 +296,7 @@ describe("Test Authorizations", () => { expect(location.pathname).to.not.eq("/app/admin/stats"); }); cy.checkalert( - "Permission denied: you are not authorized to access this page" + "Permission denied: you are not authorized to access this page", ); cy.login(email, pwd); //////////////////////////////////////////////////////////////////// @@ -310,7 +305,7 @@ describe("Test Authorizations", () => { expect(location.pathname).to.not.eq("/app/admin/mail"); }); cy.checkalert( - "Permission denied: you are not authorized to access this page" + "Permission denied: you are not authorized to access this page", ); cy.login(email, pwd); //////////////////////////////////////////////////////////////////// @@ -319,13 +314,11 @@ describe("Test Authorizations", () => { expect(location.pathname).to.not.eq("/app/group/users"); }); cy.checkalert( - "Permission denied: you are not authorized to access this page" + "Permission denied: you are not authorized to access this page", ); - // cy.login(email, pwd); //////////////////////////////////////////////////////////////////// // Delete temporary user - // cy.logout(); cy.login(); cy.deleteuser(email); }); diff --git a/cypress/e2e/group_users.spec.ts b/cypress/e2e/group_users.spec.ts index d3cebdace..49080d9c1 100644 --- a/cypress/e2e/group_users.spec.ts +++ b/cypress/e2e/group_users.spec.ts @@ -1,6 +1,6 @@ // This is to silence ESLint about undefined cy /*global cy, Cypress*/ -import { getpassword, get_random_username } from "../../fixtures/utilities"; +import { getpassword, get_random_username } from "../fixtures/utilities"; describe("GroupUsers", () => { it("Test Group Users", () => { diff --git a/cypress/e2e/kitchen.sink.spec.ts b/cypress/e2e/kitchen.sink.spec.ts index de6bc519b..56f5cfae6 100644 --- a/cypress/e2e/kitchen.sink.spec.ts +++ b/cypress/e2e/kitchen.sink.spec.ts @@ -49,7 +49,7 @@ describe("KitchenSink", () => { cy.get("input[ngbdatepicker]").click(); cy.get( - 'ngb-datepicker-navigation-select select[title="Select year"]' + 'ngb-datepicker-navigation-select select[title="Select year"]', ).as("year"); const current_year = new Date().getFullYear(); @@ -81,7 +81,7 @@ describe("KitchenSink", () => { cy.get("@year").select((current_year + 1).toString()); cy.get( - 'ngb-datepicker-navigation-select select[title="Select month"]' + 'ngb-datepicker-navigation-select select[title="Select month"]', ).select("5"); cy.get("div.ngb-dp-day div").contains("19").click({ force: true }); @@ -104,13 +104,13 @@ describe("KitchenSink", () => { cy.get("@url").clear().type("www.google.co"); cy.get("formly-validation-message").should( "not.contain", - "Invalid web address" + "Invalid web address", ); cy.get("@url").clear().type("wwwgoogle.com"); cy.get("formly-validation-message").should( "not.contain", - "Invalid web address" + "Invalid web address", ); // Not allowed in cypress... @@ -119,13 +119,13 @@ describe("KitchenSink", () => { cy.get("@url").clear().type("http://www.google.com"); cy.get("formly-validation-message").should( "not.contain", - "Invalid web address" + "Invalid web address", ); cy.get("@url").clear().type("https://www.google.com"); cy.get("formly-validation-message").should( "not.contain", - "Invalid web address" + "Invalid web address", ); cy.get("@url").clear().type("httpx://www.google.com"); @@ -134,19 +134,19 @@ describe("KitchenSink", () => { cy.get("@url").clear().type("ftp://www.google.com"); cy.get("formly-validation-message").should( "not.contain", - "Invalid web address" + "Invalid web address", ); cy.get("@url").clear().type("user@sample.org"); cy.get("formly-validation-message").should( "not.contain", - "Invalid web address" + "Invalid web address", ); cy.get("@url").clear().type("www.google.com"); cy.get("formly-validation-message").should( "not.contain", - "Invalid web address" + "Invalid web address", ); cy.get("@text").clear().type("123"); @@ -154,7 +154,7 @@ describe("KitchenSink", () => { cy.get("@text").clear().type("1234"); cy.get("formly-validation-message").should( "not.contain", - "Should have at least 4 characters" + "Should have at least 4 characters", ); cy.get("@text").clear().type("12345678"); // due to max: 6 on field definition @@ -163,7 +163,7 @@ describe("KitchenSink", () => { // the input does not permit to include more than specified max cy.get("formly-validation-message").should( "not.contain", - "Should have no more than 6 characters" + "Should have no more than 6 characters", ); cy.get("@number").clear().type("0"); @@ -173,32 +173,32 @@ describe("KitchenSink", () => { cy.get("@number").clear().type("5"); cy.get("formly-validation-message").should( "not.contain", - "Should be greater than 1" + "Should be greater than 1", ); cy.get("formly-validation-message").should( "not.contain", - "Should be lower than 9" + "Should be lower than 9", ); cy.get("@number").clear().type("2.5"); cy.get("formly-validation-message").should( "not.contain", - "Should be greater than 1" + "Should be greater than 1", ); cy.get("formly-validation-message").should( "not.contain", - "Should be lower than 9" + "Should be lower than 9", ); // 30e-1 == 3 cy.get("@number").clear().type("30e-1"); cy.get("formly-validation-message").should( "not.contain", - "Should be greater than 1" + "Should be greater than 1", ); cy.get("formly-validation-message").should( "not.contain", - "Should be lower than 9" + "Should be lower than 9", ); // Let's remove the validation error introduced to ease check of missing errors @@ -335,7 +335,7 @@ describe("KitchenSink", () => { cy.get("@date").click(); cy.get( - 'ngb-datepicker-navigation-select select[title="Select year"]' + 'ngb-datepicker-navigation-select select[title="Select year"]', ).as("year"); const current_year = new Date().getFullYear(); @@ -345,7 +345,7 @@ describe("KitchenSink", () => { cy.get("@year").select((current_year + 1).toString()); cy.get( - 'ngb-datepicker-navigation-select select[title="Select month"]' + 'ngb-datepicker-navigation-select select[title="Select month"]', ).select("5"); cy.get("div.ngb-dp-day div").contains("19").click({ force: true }); diff --git a/cypress/e2e/login.ban.spec.ts b/cypress/e2e/login.ban.spec.ts index 8703a8a37..7498d8e4f 100644 --- a/cypress/e2e/login.ban.spec.ts +++ b/cypress/e2e/login.ban.spec.ts @@ -5,7 +5,7 @@ import { getpassword, get_random_username, get_totp, -} from "../../fixtures/utilities"; +} from "../fixtures/utilities"; describe("Login Ban", () => { if (Cypress.env("AUTH_MAX_LOGIN_ATTEMPTS") > 0) { @@ -19,10 +19,10 @@ describe("Login Ban", () => { cy.get("div.card-header h1").contains("Invalid unlock token"); cy.get("div.card-body p").contains( - "The received token is not valid, if you copied the URL please verify that you copied all parts of it." + "The received token is not valid, if you copied the URL please verify that you copied all parts of it.", ); cy.get("div.card-body p").contains( - "If the URL is correct the token could be invalid because your credentials are already unlocked." + "If the URL is correct the token could be invalid because your credentials are already unlocked.", ); cy.get("div.card-body p").contains("To verify that you can try to"); @@ -60,14 +60,14 @@ describe("Login Ban", () => { .type(pwd, { parseSpecialCharSequences: false }); cy.get("button").contains("Login").click(); cy.checkalert( - "Sorry, this account is temporarily blocked due to the number of failed login attempts." + "Sorry, this account is temporarily blocked due to the number of failed login attempts.", ); cy.visit("/public/reset"); cy.get("input[id=formly_1_input_reset_email_0]").clear().type(email); cy.get("button:contains('Submit request')").click(); cy.checkalert( - "Sorry, this account is temporarily blocked due to the number of failed login attempts." + "Sorry, this account is temporarily blocked due to the number of failed login attempts.", ); cy.getmail().then((body) => { @@ -86,7 +86,6 @@ describe("Login Ban", () => { cy.login(email, pwd); cy.goto_profile(); - cy.logout(); }); cy.login(); @@ -102,7 +101,7 @@ describe("Login Ban", () => { // 10 is the default used by backend (services/authentication) in testing mode const max_failures = Math.max( Cypress.env("AUTH_MAX_LOGIN_ATTEMPTS"), - 10 + 10, ); cy.visit("/app/login"); @@ -131,14 +130,14 @@ describe("Login Ban", () => { .type(get_totp()); cy.get("button").contains("Authorize").click(); cy.checkalert( - "Sorry, this account is temporarily blocked due to the number of failed login attempts." + "Sorry, this account is temporarily blocked due to the number of failed login attempts.", ); cy.visit("/public/reset"); cy.get("input[id=formly_1_input_reset_email_0]").clear().type(email); cy.get("button:contains('Submit request')").click(); cy.checkalert( - "Sorry, this account is temporarily blocked due to the number of failed login attempts." + "Sorry, this account is temporarily blocked due to the number of failed login attempts.", ); cy.getmail().then((body) => { @@ -157,7 +156,6 @@ describe("Login Ban", () => { cy.login(email, pwd); cy.goto_profile(); - cy.logout(); }); cy.login(); diff --git a/cypress/e2e/login.expired.spec.ts b/cypress/e2e/login.expired.spec.ts index 2e490135a..4c5ad3c6c 100644 --- a/cypress/e2e/login.expired.spec.ts +++ b/cypress/e2e/login.expired.spec.ts @@ -1,7 +1,7 @@ // This is to silence ESLint about undefined cy /*global cy, Cypress*/ -import { getpassword, get_random_username } from "../../fixtures/utilities"; +import { getpassword, get_random_username } from "../fixtures/utilities"; describe("AccountExpired", () => { it("LOGIN EXPIRED", () => { @@ -31,7 +31,7 @@ describe("AccountExpired", () => { cy.intercept("POST", "/auth/reset").as("reset"); cy.get( - 'input[placeholder="Type here your email address to receive the reset link"]' + 'input[placeholder="Type here your email address to receive the reset link"]', ) .clear() .type(email); @@ -64,15 +64,15 @@ describe("AccountExpired", () => { .click({ force: true }); // Open the datepicker cy.get( - 'input[placeholder="This user will be blocked after this date"]' + 'input[placeholder="This user will be blocked after this date"]', ).click(); // Select the 31 - 12 - 2050 (the max allowed) cy.get( - 'ngb-datepicker-navigation-select select[title="Select year"]' + 'ngb-datepicker-navigation-select select[title="Select year"]', ).select("2050"); cy.get( - 'ngb-datepicker-navigation-select select[title="Select month"]' + 'ngb-datepicker-navigation-select select[title="Select month"]', ).select("12"); cy.get("div.ngb-dp-day div").contains("31").click({ force: true }); cy.get('button:contains("Submit")').click({ force: true }); diff --git a/cypress/e2e/login.failed.spec.ts b/cypress/e2e/login.failed.spec.ts index dce45042e..7ed99015f 100644 --- a/cypress/e2e/login.failed.spec.ts +++ b/cypress/e2e/login.failed.spec.ts @@ -72,7 +72,7 @@ describe("FailedLogin", () => { cy.wait("@login1"); cy.checkalert( - "Unable to login due to a server error. If this error persists please contact system administrators" + "Unable to login due to a server error. If this error persists please contact system administrators", ); cy.intercept("POST", "/auth/login", { diff --git a/cypress/e2e/login.mocked.spec.ts b/cypress/e2e/login.mocked.spec.ts index 29139ec3d..9a0f19def 100644 --- a/cypress/e2e/login.mocked.spec.ts +++ b/cypress/e2e/login.mocked.spec.ts @@ -1,7 +1,7 @@ // This is to silence ESLint about undefined cy /*global cy, Cypress*/ -import { getpassword } from "../../fixtures/utilities"; +import { getpassword } from "../fixtures/utilities"; describe("Mocked login", () => { beforeEach(() => { @@ -9,10 +9,10 @@ describe("Mocked login", () => { cy.closecookielaw(); cy.get("input[placeholder='Your username (email)']").type( - Cypress.env("AUTH_DEFAULT_USERNAME") + Cypress.env("AUTH_DEFAULT_USERNAME"), ); cy.get("input[placeholder='Your password']").type( - Cypress.env("AUTH_DEFAULT_PASSWORD") + Cypress.env("AUTH_DEFAULT_PASSWORD"), ); }); diff --git a/cypress/e2e/login.password.expired.spec.ts b/cypress/e2e/login.password.expired.spec.ts index bb144bffa..ca711e8ae 100644 --- a/cypress/e2e/login.password.expired.spec.ts +++ b/cypress/e2e/login.password.expired.spec.ts @@ -5,7 +5,7 @@ import { getpassword, get_random_username, get_totp, -} from "../../fixtures/utilities"; +} from "../fixtures/utilities"; describe("Login", () => { // do not directly create the random values here, @@ -27,49 +27,35 @@ describe("Login", () => { cy.get("input[placeholder='Your username (email)']").as("user"); cy.get("input[placeholder='Your password']").as("pwd"); - // Cypress is still not able to undo an intercept.. - // A single intercept is needed here, - // then the test should continue with normal responses - // TO BE REMOVED: - cy.server(); - cy.route({ - method: "POST", - url: "/auth/login", - status: 403, - response: { - actions: ["PASSWORD EXPIRED"], - errors: ["Your password is expired, please change it"], + cy.intercept( + // routeMatcher + { + path: "/auth/login", + method: "POST", + times: 1, }, - }); - - // TO BE REPLACED WITH: - // cy.intercept("POST", "/auth/login", { - // statusCode: 403, - // body: { - // actions: ["PASSWORD EXPIRED"], - // errors: ["Your password is expired, please change it"], - // }, - // }).as("login"); + // staticResponse + { + statusCode: 403, + body: { + actions: ["PASSWORD EXPIRED"], + errors: ["Your password is expired, please change it"], + }, + }, + ).as("login"); cy.get("@user").type(email); cy.get("@pwd").type(pwd, { parseSpecialCharSequences: false }); cy.get("button").contains("Login").click(); - // TO BE ADDED - // cy.wait("@login"); + cy.wait("@login"); cy.location().should((location) => { expect(location.pathname).to.eq("/app/login"); }); - // TO BE REMOVED: - cy.server({ enable: false }); - - // TO BE REPLACED WITH SOMETHING TO UNDO THE PREVIOUS INTERCEPT - // cy.intercept("POST", "/auth/login"); - cy.checkalert("Your password is expired, please change it"); cy.get("div.card-header h1").contains( - "Your password is expired, please change it" + "Your password is expired, please change it", ); cy.get("button").contains("Change").click(); @@ -84,7 +70,7 @@ describe("Login", () => { 0, "Should have at least " + Cypress.env("AUTH_MIN_PASSWORD_LENGTH") + - " characters" + " characters", ); cy.get("button").contains("Change").click(); @@ -183,8 +169,6 @@ describe("Login", () => { } else { cy.get("button").contains("Change").click(); } - - cy.logout(); }); after(() => { diff --git a/cypress/e2e/login.spec.ts b/cypress/e2e/login.spec.ts index 8241fc3b4..e7556195f 100644 --- a/cypress/e2e/login.spec.ts +++ b/cypress/e2e/login.spec.ts @@ -4,7 +4,7 @@ import { getpassword, get_random_username, get_totp, -} from "../../fixtures/utilities"; +} from "../fixtures/utilities"; describe("SuccessfulLogin", () => { // do not directly create the random values here, @@ -84,7 +84,7 @@ describe("SuccessfulLogin", () => { cy.get("a").find(".fa-user"); cy.get("table").find("td").contains(email); - cy.logout(); + cy.logout(false); // After the logout you are automatically redirected to the default page... // more in generale not on the profile page diff --git a/cypress/e2e/login.totp.spec.ts b/cypress/e2e/login.totp.spec.ts index 748bdc15f..dc9ef61ca 100644 --- a/cypress/e2e/login.totp.spec.ts +++ b/cypress/e2e/login.totp.spec.ts @@ -5,7 +5,7 @@ import { getpassword, get_random_username, get_totp, -} from "../../fixtures/utilities"; +} from "../fixtures/utilities"; if (Cypress.env("AUTH_SECOND_FACTOR_AUTHENTICATION")) { describe("Login with TOTP", () => { @@ -38,10 +38,10 @@ if (Cypress.env("AUTH_SECOND_FACTOR_AUTHENTICATION")) { cy.checkalert("You do not provided a valid verification code"); cy.get("div.card-header h1").contains( - "Configure Two-Factor with Google Authenticator" + "Configure Two-Factor with Google Authenticator", ); cy.get("div.card-header.bg-warning h1").contains( - "Please change your temporary password" + "Please change your temporary password", ); cy.checkalert("Please change your temporary password"); @@ -98,7 +98,7 @@ if (Cypress.env("AUTH_SECOND_FACTOR_AUTHENTICATION")) { if (Cypress.env("ALLOW_TERMS_OF_USE")) { cy.get("div.modal-footer h1").contains( - "Do you accept our Terms of Use?" + "Do you accept our Terms of Use?", ); cy.get("div.modal-footer button").first().contains("YES").click(); } @@ -107,8 +107,6 @@ if (Cypress.env("AUTH_SECOND_FACTOR_AUTHENTICATION")) { cy.goto_profile(); cy.get("table").find("td").contains(email); - - cy.logout(); }); it("TOTP - login", () => { @@ -127,7 +125,7 @@ if (Cypress.env("AUTH_SECOND_FACTOR_AUTHENTICATION")) { cy.get("input[placeholder='Your password']").should("not.exist"); cy.get("div.card-header.bg-warning h1").contains( - "Provide the verification code" + "Provide the verification code", ); // Authorization code is missing @@ -209,7 +207,7 @@ if (Cypress.env("AUTH_SECOND_FACTOR_AUTHENTICATION")) { .clear() .type(pwd + "!"); cy.get( - 'input[placeholder="Type again the new password for confirmation"]' + 'input[placeholder="Type again the new password for confirmation"]', ) .clear() .type(pwd + "!"); @@ -274,7 +272,7 @@ if (Cypress.env("AUTH_SECOND_FACTOR_AUTHENTICATION")) { cy.wait("@login"); cy.get("div.card-header.bg-warning h1").contains( - "Provide the verification code" + "Provide the verification code", ); cy.get("input[placeholder='TOTP verification code']") .clear() @@ -288,8 +286,6 @@ if (Cypress.env("AUTH_SECOND_FACTOR_AUTHENTICATION")) { }); after(() => { - cy.logout(); - cy.login(); cy.deleteuser(email); }); diff --git a/cypress/e2e/profile.change.password.spec.ts b/cypress/e2e/profile.change.password.spec.ts index 0c08dbd93..4dc5cf939 100644 --- a/cypress/e2e/profile.change.password.spec.ts +++ b/cypress/e2e/profile.change.password.spec.ts @@ -5,7 +5,7 @@ import { getpassword, get_random_username, get_totp, -} from "../../fixtures/utilities"; +} from "../fixtures/utilities"; describe("ChangePassword", () => { // do not directly create the random values here, @@ -53,10 +53,10 @@ describe("ChangePassword", () => { cy.checkvalidation(0, "This field is required"); cy.get('input[placeholder="Type the desidered new password"]').as( - "new_password" + "new_password", ); cy.get( - 'input[placeholder="Type again the new password for confirmation"]' + 'input[placeholder="Type again the new password for confirmation"]', ).as("confirm_password"); cy.get("@new_password").clear().type("short"); @@ -77,7 +77,7 @@ describe("ChangePassword", () => { 0, "Should have at least " + Cypress.env("AUTH_MIN_PASSWORD_LENGTH") + - " characters" + " characters", ); let newPassword = getpassword(1); @@ -190,8 +190,6 @@ describe("ChangePassword", () => { }); after(() => { - cy.logout(); - cy.login(); cy.deleteuser(email); }); diff --git a/cypress/e2e/profile.change.temporary.password.spec.ts b/cypress/e2e/profile.change.temporary.password.spec.ts index 37109b327..4e9a6fbf6 100644 --- a/cypress/e2e/profile.change.temporary.password.spec.ts +++ b/cypress/e2e/profile.change.temporary.password.spec.ts @@ -5,7 +5,7 @@ import { getpassword, get_random_username, get_totp, -} from "../../fixtures/utilities"; +} from "../fixtures/utilities"; if (Cypress.env("AUTH_FORCE_FIRST_PASSWORD_CHANGE") === 1) { describe("ChangeTemporaryPassword", () => { @@ -36,17 +36,17 @@ if (Cypress.env("AUTH_FORCE_FIRST_PASSWORD_CHANGE") === 1) { cy.get("button").contains("Authorize").as("submit"); cy.get("div.card-header h1").contains( - "Configure Two-Factor with Google Authenticator" + "Configure Two-Factor with Google Authenticator", ); cy.get("div.card-header.bg-warning h1").contains( - "Please change your temporary password" + "Please change your temporary password", ); cy.checkalert("You do not provided a valid verification code"); cy.get("input[placeholder='TOTP verification code']").type(get_totp()); } else { cy.get("div.card-header.bg-warning h1").contains( - "Please change your temporary password" + "Please change your temporary password", ); cy.get('button:contains("Change")').as("submit"); @@ -65,7 +65,7 @@ if (Cypress.env("AUTH_FORCE_FIRST_PASSWORD_CHANGE") === 1) { 0, "Should have at least " + Cypress.env("AUTH_MIN_PASSWORD_LENGTH") + - " characters" + " characters", ); let newPassword = getpassword(1); @@ -124,7 +124,7 @@ if (Cypress.env("AUTH_FORCE_FIRST_PASSWORD_CHANGE") === 1) { if (Cypress.env("ALLOW_TERMS_OF_USE")) { cy.get("div.modal-footer h1").contains( - "Do you accept our Terms of Use?" + "Do you accept our Terms of Use?", ); cy.get("div.modal-footer button").first().contains("YES").click(); } @@ -132,8 +132,6 @@ if (Cypress.env("AUTH_FORCE_FIRST_PASSWORD_CHANGE") === 1) { cy.goto_profile(); cy.get("table").find("td").contains(email); - cy.logout(); - cy.login(email, pwd + "!new!"); cy.goto_profile(); @@ -141,8 +139,6 @@ if (Cypress.env("AUTH_FORCE_FIRST_PASSWORD_CHANGE") === 1) { }); after(() => { - cy.logout(); - cy.login(); cy.deleteuser(email); }); diff --git a/cypress/e2e/registration.spec.ts b/cypress/e2e/registration.spec.ts index cd8a117f6..df5102cc3 100644 --- a/cypress/e2e/registration.spec.ts +++ b/cypress/e2e/registration.spec.ts @@ -5,7 +5,7 @@ import { getpassword, get_random_username, get_totp, -} from "../../fixtures/utilities"; +} from "../fixtures/utilities"; describe("Registration", () => { if (!Cypress.env("ALLOW_REGISTRATION")) { @@ -79,10 +79,10 @@ describe("Registration", () => { cy.get('input[placeholder="Type here your surname"]').as("surname"); cy.get('input[placeholder="Type here your email address"]').as("email"); cy.get('input[placeholder="Type here the desidered password"]').as( - "password" + "password", ); cy.get( - 'input[placeholder="Type again the desidered password for confirmation"]' + 'input[placeholder="Type again the desidered password for confirmation"]', ).as("confirmation"); cy.get('button:contains("Register")').as("submit"); @@ -109,7 +109,7 @@ describe("Registration", () => { 1, "Should have at least " + Cypress.env("AUTH_MIN_PASSWORD_LENGTH") + - " characters" + " characters", ); cy.get("@password") @@ -177,7 +177,7 @@ describe("Registration", () => { cy.get("@submit").click({ force: true }); cy.checkalert( - "This user already exists: " + Cypress.env("AUTH_DEFAULT_USERNAME") + "This user already exists: " + Cypress.env("AUTH_DEFAULT_USERNAME"), ); // Failures on password validation: missing upper case letters @@ -238,7 +238,7 @@ describe("Registration", () => { cy.get("div.card-header h1").contains("Account registered"); cy.contains( - "User successfully registered. You will receive an email to confirm your registraton and activate your account" + "User successfully registered. You will receive an email to confirm your registraton and activate your account", ); }); @@ -259,14 +259,14 @@ describe("Registration", () => { cy.wait("@login"); cy.get("div.card-header.bg-warning h1").contains( - "This account is not active" + "This account is not active", ); cy.get("div.card-body").contains("Didn't receive an activation link?"); cy.get("a").contains("Click here to send again").click({ force: true }); cy.checkalert( - "We are sending an email to your email address where you will find the link to activate your account" + "We are sending an email to your email address where you will find the link to activate your account", ); cy.location().should((location) => { @@ -284,7 +284,7 @@ describe("Registration", () => { cy.get("div.card-header h1").contains("Invalid activation token"); cy.get("div.card-body").contains( - "This activation token is not valid and your request cannot be satisfied." + "This activation token is not valid and your request cannot be satisfied.", ); cy.getmail().then((body) => { @@ -303,7 +303,7 @@ describe("Registration", () => { cy.get("div.card-header h1").contains("Invalid activation token"); cy.get("div.card-body").contains( - "This activation token is not valid and your request cannot be satisfied." + "This activation token is not valid and your request cannot be satisfied.", ); }); }); @@ -324,21 +324,21 @@ describe("Registration", () => { if (Cypress.env("AUTH_SECOND_FACTOR_AUTHENTICATION")) { cy.get("div.card-header h1").contains( - "Configure Two-Factor with Google Authenticator" + "Configure Two-Factor with Google Authenticator", ); cy.get("input[placeholder='Your new password']").type( - newPassword + "!" + newPassword + "!", ); cy.get("input[placeholder='Confirm your new password']").type( - newPassword + "!" + newPassword + "!", ); cy.get("input[placeholder='TOTP verification code']").type(get_totp()); cy.get("button").contains("Authorize").click(); } else if (Cypress.env("AUTH_FORCE_FIRST_PASSWORD_CHANGE") === 1) { cy.get("div.card-header.bg-warning h1").contains( - "Please change your temporary password" + "Please change your temporary password", ); cy.checkalert("Please change your temporary password"); @@ -359,16 +359,14 @@ describe("Registration", () => { cy.visit("/app/admin/users"); cy.checkalert( - "Permission denied: you are not authorized to access this page" + "Permission denied: you are not authorized to access this page", ); cy.visit("/app/admin/sessions"); cy.checkalert( - "Permission denied: you are not authorized to access this page" + "Permission denied: you are not authorized to access this page", ); - - cy.logout(); }); after(() => { diff --git a/cypress/e2e/reset.password.spec.ts b/cypress/e2e/reset.password.spec.ts index ceee1dc5f..463074eaa 100644 --- a/cypress/e2e/reset.password.spec.ts +++ b/cypress/e2e/reset.password.spec.ts @@ -5,7 +5,7 @@ import { getpassword, get_random_username, get_totp, -} from "../../fixtures/utilities"; +} from "../fixtures/utilities"; if (Cypress.env("ALLOW_PASSWORD_RESET")) { describe("ResetPassword", () => { @@ -37,7 +37,7 @@ if (Cypress.env("ALLOW_PASSWORD_RESET")) { cy.checkvalidation(0, "This field is required"); cy.get( - 'input[placeholder="Type here your email address to receive the reset link"]' + 'input[placeholder="Type here your email address to receive the reset link"]', ).as("email"); cy.get("@email").clear().type("invalid"); @@ -48,7 +48,7 @@ if (Cypress.env("ALLOW_PASSWORD_RESET")) { cy.get("button:contains('Submit request')").click(); cy.checkalert( - "Sorry, invalid@sample.com is not recognized as a valid username" + "Sorry, invalid@sample.com is not recognized as a valid username", ); cy.intercept("POST", "/auth/reset").as("reset"); @@ -60,11 +60,11 @@ if (Cypress.env("ALLOW_PASSWORD_RESET")) { cy.get("div.card-header h1").contains("Reset your password"); cy.get("div.card-body").contains( - "We'll send instructions to the email provided if it's associated with an account. Please check your spam/junk folder." + "We'll send instructions to the email provided if it's associated with an account. Please check your spam/junk folder.", ); cy.intercept("PUT", "/auth/reset/token-received-by-email").as( - "validate1" + "validate1", ); cy.visit("/public/reset/token-received-by-email"); @@ -88,10 +88,10 @@ if (Cypress.env("ALLOW_PASSWORD_RESET")) { cy.checkvalidation(1, "This field is required"); cy.get('input[placeholder="Type here your new password"]').as( - "new_password" + "new_password", ); cy.get( - 'input[placeholder="Type again your new password for confirmation"]' + 'input[placeholder="Type again your new password for confirmation"]', ).as("confirm_password"); cy.get("@new_password").clear().type("short"); @@ -101,7 +101,7 @@ if (Cypress.env("ALLOW_PASSWORD_RESET")) { 0, "Should have at least " + Cypress.env("AUTH_MIN_PASSWORD_LENGTH") + - " characters" + " characters", ); cy.checkvalidation(1, "This field is required"); @@ -152,7 +152,7 @@ if (Cypress.env("ALLOW_PASSWORD_RESET")) { cy.get("@confirm_password").clear().type(newPassword); cy.get("button:contains('Submit')").click(); cy.checkalert( - "Password successfully changed. Please login with your new password" + "Password successfully changed. Please login with your new password", ); cy.location().should((location) => { @@ -179,7 +179,7 @@ if (Cypress.env("ALLOW_PASSWORD_RESET")) { if (Cypress.env("AUTH_SECOND_FACTOR_AUTHENTICATION")) { cy.get("input[placeholder='Your password']").should("not.exist"); cy.get("div.card-header h1").contains( - "Provide the verification code" + "Provide the verification code", ); cy.get("input[placeholder='TOTP verification code']") .clear() @@ -193,8 +193,6 @@ if (Cypress.env("ALLOW_PASSWORD_RESET")) { }); after(() => { - cy.logout(); - cy.login(); cy.deleteuser(email); }); diff --git a/cypress/e2e/responsive.spec.ts b/cypress/e2e/responsive.spec.ts index c738453c9..04ba582c5 100644 --- a/cypress/e2e/responsive.spec.ts +++ b/cypress/e2e/responsive.spec.ts @@ -1,7 +1,7 @@ // This is to silence ESLint about undefined cy /*global cy, Cypress*/ -import { get_totp } from "../../fixtures/utilities"; +import { get_totp } from "../fixtures/utilities"; describe("Responsive tests", () => { let expected_collapsed_navbar = false; @@ -78,8 +78,5 @@ describe("Responsive tests", () => { }); cy.get("div.card-header h1").contains("Your profile"); - - // Logout Confirmation modal is not opened on Cypress when navbar is collapsed... - // cy.logout(expected_collapsed_navbar); }); }); diff --git a/cypress/e2e/terms.of.use.acceptance.spec.ts b/cypress/e2e/terms.of.use.acceptance.spec.ts index 4d0c97f24..774f2557f 100644 --- a/cypress/e2e/terms.of.use.acceptance.spec.ts +++ b/cypress/e2e/terms.of.use.acceptance.spec.ts @@ -5,7 +5,7 @@ import { getpassword, get_random_username, get_totp, -} from "../../fixtures/utilities"; +} from "../fixtures/utilities"; if (Cypress.env("ALLOW_TERMS_OF_USE")) { describe("Terms of use", () => { @@ -37,7 +37,7 @@ if (Cypress.env("ALLOW_TERMS_OF_USE")) { cy.checkalert("You do not provided a valid verification code"); cy.get("div.card-header h1").contains( - "Configure Two-Factor with Google Authenticator" + "Configure Two-Factor with Google Authenticator", ); pwd = pwd + "!"; @@ -59,7 +59,7 @@ if (Cypress.env("ALLOW_TERMS_OF_USE")) { cy.wait("@login"); } else if (Cypress.env("AUTH_FORCE_FIRST_PASSWORD_CHANGE") === 1) { cy.get("div.card-header.bg-warning h1").contains( - "Please change your temporary password" + "Please change your temporary password", ); cy.checkalert("Please change your temporary password"); @@ -83,7 +83,7 @@ if (Cypress.env("ALLOW_TERMS_OF_USE")) { cy.get("div.modal-footer button").last().contains("NO").click(); cy.checkalert( - "We apologize but you are not allowed to login, as you have not accepted our Terms of Use" + "We apologize but you are not allowed to login, as you have not accepted our Terms of Use", ); }); @@ -154,8 +154,6 @@ if (Cypress.env("ALLOW_TERMS_OF_USE")) { }); after(() => { - cy.logout(); - cy.login(); cy.visit("/app/admin/users"); diff --git a/cypress/e2e/zzz_inactivity_ban.spec.ts b/cypress/e2e/zzz_inactivity_ban.spec.ts index c11c2e948..5a2382734 100644 --- a/cypress/e2e/zzz_inactivity_ban.spec.ts +++ b/cypress/e2e/zzz_inactivity_ban.spec.ts @@ -1,7 +1,7 @@ // This is to silence ESLint about undefined cy /*global cy, Cypress*/ -import { getpassword, get_totp } from "../../fixtures/utilities"; +import { getpassword, get_totp } from "../fixtures/utilities"; if (Cypress.env("AUTH_DISABLE_UNUSED_CREDENTIALS_AFTER")) { describe("Test inactivity ban", () => { diff --git a/cypress/fixtures/utilities.ts b/cypress/fixtures/utilities.ts index d61efc1b1..408473ae9 100644 --- a/cypress/fixtures/utilities.ts +++ b/cypress/fixtures/utilities.ts @@ -1,7 +1,53 @@ // This is to silence ESLint about undefined Cypress /*global cy, Cypress*/ -import * as generator from "generate-password-browser"; +function generatePassword( + length: number, + useUppercase: boolean, + useNumbers: boolean, + useSymbols: boolean, +): string { + const lowercase = "abcdefghijklmnopqrstuvwxyz"; + const uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + const numbers = "0123456789"; + const symbols = "!@#$%^&*()-_=+[]{}|;:',.<>/?"; + + let password = []; + + let charset = lowercase; + password.push(lowercase.charAt(Math.floor(Math.random() * lowercase.length))); + + if (useUppercase) { + charset += uppercase; + password.push( + uppercase.charAt(Math.floor(Math.random() * uppercase.length)), + ); + } + if (useNumbers) { + charset += numbers; + password.push(numbers.charAt(Math.floor(Math.random() * numbers.length))); + } + if (useSymbols) { + charset += symbols; + password.push(symbols.charAt(Math.floor(Math.random() * symbols.length))); + } + + if (charset === "") { + throw new Error("At least one character type should be selected"); + } + + for (let i = password.length, n = charset.length; i < length; ++i) { + password.push(charset.charAt(Math.floor(Math.random() * n))); + } + + // Shuffle the password array + for (let i = password.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [password[i], password[j]] = [password[j], password[i]]; // Swap + } + + return password.join(""); +} // type can be: // 1 = only lower case letters @@ -13,30 +59,11 @@ export function getpassword(type, len = 0) { len = Cypress.env("AUTH_MIN_PASSWORD_LENGTH"); } - const password = generator.generate({ - length: len, - lowercase: true, - uppercase: type >= 2, - numbers: type >= 3, - symbols: type >= 4, - excludeSimilarCharacters: false, - strict: true, - }); - - return password; + return generatePassword(len, type >= 2, type >= 3, type >= 4); } export function get_random_username(prefix: string) { - const randval = generator.generate({ - length: 16, - lowercase: true, - uppercase: false, - numbers: false, - symbols: false, - excludeSimilarCharacters: false, - strict: false, - }); - + const randval = generatePassword(16, false, false, false); return `${prefix}_${randval}@sample.org`; } diff --git a/cypress/support/e2e.js b/cypress/support/e2e.js index 36349e9c7..39c194363 100644 --- a/cypress/support/e2e.js +++ b/cypress/support/e2e.js @@ -91,7 +91,7 @@ Cypress.Commands.add("change_expired_password", (email, pwd, formtype) => { cy.get("input[placeholder='Your password']").should("not.exist"); cy.get("div.card-header.bg-warning h1").contains( - "Provide the verification code" + "Provide the verification code", ); cy.get("input[placeholder='TOTP verification code']") .clear() @@ -193,7 +193,7 @@ Cypress.Commands.add("login_and_init_user", (email = null, pwd = null) => { if (Cypress.env("AUTH_SECOND_FACTOR_AUTHENTICATION")) { cy.get("div.card-header h1").contains( - "Configure Two-Factor with Google Authenticator" + "Configure Two-Factor with Google Authenticator", ); cy.checkalert("Please change your temporary password"); @@ -215,7 +215,7 @@ Cypress.Commands.add("login_and_init_user", (email = null, pwd = null) => { cy.wait(200); } else if (Cypress.env("AUTH_FORCE_FIRST_PASSWORD_CHANGE") === 1) { cy.get("div.card-header.bg-warning h1").contains( - "Please change your temporary password" + "Please change your temporary password", ); cy.checkalert("Please change your temporary password"); @@ -239,32 +239,22 @@ Cypress.Commands.add("login_and_init_user", (email = null, pwd = null) => { cy.wait(300); }); -Cypress.Commands.add("logout", (collapsed = false) => { - if (collapsed) { - cy.get("button.navbar-toggler").click(); - - // cy.get("a").find(".fa-right-from-bracket").parent().click({ force: true }); - // cy.get("div.modal-footer") - // .find("button") - // .contains("Confirm") - // .click({ force: true }); - - cy.get("i.fa-right-from-bracket").parent().click(); - cy.scrollTo("top"); +// With Angular 15 I'm experiencing issues with the logout modal +// that often does not trigger the modal opening when clicked +// Warning: at the moment all logouts have via_modal false !! +Cypress.Commands.add("logout", (via_modal = true) => { + if (via_modal) { + cy.get("button[id=logout-icon]").click({ force: true }); cy.get("button").contains("Confirm").click(); } else { - cy.get("i.fa-right-from-bracket").parent().click(); - cy.get("button").contains("Confirm").click(); + // access to login page triggers tokens invalidation + cy.visit("/app/login"); } }); // Replaces cy.visit("/app/profile") to introduces automatic waits on DOM elements // instead of requiring waits on the http call -Cypress.Commands.add("goto_profile", (collapsed = false) => { - if (collapsed) { - cy.get("button.navbar-toggler").click(); - } - +Cypress.Commands.add("goto_profile", () => { // Why this wait? // Cypress does not offer a way to automatically wait for all pending XHR requests and // often some requests e.g. GET /auth/status, are still under the hook when this click @@ -299,7 +289,9 @@ Cypress.Commands.add("closecookielaw", (quiet = false) => { }); Cypress.Commands.add("checkalert", (msg) => { - cy.get("div[role=alert]").contains(msg).click({ force: true }); + cy.get("div[role=alert]") + .contains(msg) + .click({ force: true, multiple: true }); }); Cypress.Commands.add("checkvalidation", (index, msg) => { @@ -374,14 +366,14 @@ Cypress.Commands.add( if (expired) { cy.get( - 'input[placeholder="This user will be blocked after this date"]' + 'input[placeholder="This user will be blocked after this date"]', ).click(); cy.get( - 'ngb-datepicker-navigation-select select[title="Select year"]' + 'ngb-datepicker-navigation-select select[title="Select year"]', ).select("2020"); cy.get( - 'ngb-datepicker-navigation-select select[title="Select month"]' + 'ngb-datepicker-navigation-select select[title="Select month"]', ).select("12"); cy.get("div.ngb-dp-day div").contains("31").click({ force: true }); @@ -410,7 +402,7 @@ Cypress.Commands.add( cy.checkalert("Confirmation: user successfully created"); - cy.logout(); + cy.logout(false); if (init_user) { cy.visit("/app/login"); @@ -445,7 +437,7 @@ Cypress.Commands.add( cy.get("input[placeholder='Your password']").should("not.exist"); cy.get("div.card-header h1").contains( - "Configure Two-Factor with Google Authenticator" + "Configure Two-Factor with Google Authenticator", ); cy.checkalert("Please change your temporary password"); @@ -467,7 +459,7 @@ Cypress.Commands.add( } else if (Cypress.env("AUTH_FORCE_FIRST_PASSWORD_CHANGE") === 1) { cy.get("input[placeholder='Your password']").should("not.exist"); cy.get("div.card-header.bg-warning h1").contains( - "Please change your temporary password" + "Please change your temporary password", ); cy.checkalert("Please change your temporary password"); @@ -486,15 +478,15 @@ Cypress.Commands.add( if (Cypress.env("ALLOW_TERMS_OF_USE")) { cy.get("div.modal-footer h1").contains( - "Do you accept our Terms of Use?" + "Do you accept our Terms of Use?", ); cy.get("div.modal-footer button").first().contains("YES").click(); cy.wait(300); } - cy.logout(); + cy.logout(false); } - } + }, ); Cypress.Commands.add("deleteuser", (email) => { diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json index 7dae11ed4..c5cdd1e10 100644 --- a/cypress/tsconfig.json +++ b/cypress/tsconfig.json @@ -2,8 +2,8 @@ "compilerOptions": { "strict": true, "baseUrl": "/app/node_modules/", - "target": "es5", - "lib": ["es5", "dom"], + "target": "es6", + "lib": ["es6", "dom"], "types": ["cypress"] }, "include": ["**/*.ts"] diff --git a/polyfills.ts b/polyfills.ts index e04a31f80..1a81f733f 100644 --- a/polyfills.ts +++ b/polyfills.ts @@ -1,94 +1,13 @@ /** - * This file includes polyfills needed by Angular and is loaded before the app. - * You can add your own extra polyfills to this file. - * - * This file is divided into 2 sections: - * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. - * 2. Application imports. Files imported after ZoneJS that should be loaded before your main - * file. - * - * The current setup is for so-called "evergreen" browsers; the last versions of browsers that - * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), - * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. - * - * Learn more in https://angular.io/guide/browser-support + * Angular 9 introduced a global $localize() function that needs to be loaded. + * Please add import '@angular/localize'; to your polyfills.ts file. */ -/*************************************************************************************************** - * BROWSER POLYFILLS - */ - -// Old Safari and IE -import "core-js/stable"; - -/** Evergreen browsers require these. **/ -// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove. -import "core-js/proposals/reflect-metadata"; - -/** IE10 and IE11 requires the following for NgClass support on SVG elements */ -import "angular-polyfills/dist/classlist.js"; - -/** - * Web Animations `@angular/platform-browser/animations` - * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. - * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). - **/ -// import 'web-animations-js'; +import "@angular/localize/init"; /** - * By default, zone.js will patch all possible macroTask and DomEvents - * user can disable parts of macroTask/DomEvents patch by setting following flags - * because those flags need to be set before `zone.js` being loaded, and webpack - * will put import in the top of bundle, so user need to create a separate file - * in this directory (for example: zone-flags.ts), and put the following flags - * into that file, and then add the following code before importing zone.js. - * import './zone-flags.ts'; - * - * The flags allowed in zone-flags.ts are listed here. - * - * The following flags will work for all browsers. - * - * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame - * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick - * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames - * - * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js - * with the following flag, it will bypass `zone.js` patch for IE/Edge - * - * (window as any).__Zone_enable_cross_context_check = true; - * + * To reference and create an instance of the Buffer. + * This requires to install the buffer package. */ - -/*************************************************************************************************** - -/** Zone JS is required by default for Angular itself. */ -import "zone.js/dist/zone"; -import "zone.js/dist/long-stack-trace-zone"; - -/** APPLICATION IMPORTS */ - -// import 'angular-polyfills/dist/all.js'; -import "angular-polyfills/dist/typedarray.js"; -import "angular-polyfills/dist/blob.js"; -import "angular-polyfills/dist/formdata.js"; -import "angular-polyfills/dist/intl.js"; -import "angular-polyfills/dist/shim.js"; -// This fails to import with error (on version 1.0.1): -// TypeError: Cannot set property 'true' of undefined -// import 'angular-polyfills/dist/webanimations.js'; - -/* istanbul ignore next */ -// if (typeof (window as any).global === "undefined") { -// (window as any).global = window; -// } - -// Angular 9 introduced a global $localize() function that needs to be loaded. -// Please add import '@angular/localize'; to your polyfills.ts file. -import "@angular/localize/init"; - -// required by exceljs (no longer used) -// import "regenerator-runtime/runtime"; - -/* To reference and create an instance of the Buffer. This requires to install the buffer package. */ (window as any).global = window; declare var global: any; declare var require: any; diff --git a/renovate.json b/renovate.json index f0fe12609..ca75084bd 100644 --- a/renovate.json +++ b/renovate.json @@ -4,6 +4,7 @@ "commitMessageAction": "Bump", "commitMessageTopic": "{{depName}}", "commitMessageExtra": "to {{newVersion}}", + "branchConcurrentLimit": 0, "pre-commit": { "enabled": true, "groupName": "pre-commit" diff --git a/src/angular.json b/src/angular.json index 3537e51f4..2b222c4f5 100644 --- a/src/angular.json +++ b/src/angular.json @@ -17,7 +17,7 @@ "index": "app/rapydo/index.html", "main": "app/rapydo/main.ts", "deleteOutputPath": false, - "polyfills": "polyfills.ts", + "polyfills": ["zone.js", "polyfills.ts"], "tsConfig": "tsconfig.app.json", "aot": true, "sourceMap": true, @@ -112,7 +112,7 @@ "browserTarget": "RAPyDo:build:production" }, "cypress": { - "extraWebpackConfig": "./cypress/coverage.webpack.js" + "extraWebpackConfig": "/app/cypress/coverage.webpack.js" } } }, @@ -125,12 +125,11 @@ "test": { "builder": "@angular-devkit/build-angular:karma", "options": { - "main": "app/rapydo/tests.ts", - "polyfills": "polyfills.ts", - "tsConfig": "tsconfig.spec.json", "karmaConfig": "karma.conf.js", - "assets": ["./app/rapydo/assets", "./app/custom/assets"], + "polyfills": ["zone.js", "zone.js/testing"], + "tsConfig": "tsconfig.spec.json", "styles": ["./app/rapydo/styles/rapydo.scss"], + "assets": ["./app/rapydo/assets", "./app/custom/assets"], "scripts": [] } } diff --git a/src/app/app.auth.guard.ts b/src/app/app.auth.guard.ts index 98f4e8e59..71cef6f46 100644 --- a/src/app/app.auth.guard.ts +++ b/src/app/app.auth.guard.ts @@ -16,12 +16,12 @@ export class AuthGuard implements CanActivate { constructor( public auth: AuthService, public api: ApiService, - public router: Router + public router: Router, ) {} canActivate( route: ActivatedRouteSnapshot, - state: RouterStateSnapshot + state: RouterStateSnapshot, ): Observable { const expectedRoles = route.data.roles; @@ -46,7 +46,7 @@ export class AuthGuard implements CanActivate { } return false; - }) + }), ); } } diff --git a/src/app/app.component.html b/src/app/app.component.html index 1131e8bb9..cb88a922c 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,4 +1,4 @@ - +
@@ -37,11 +37,11 @@

-