diff --git a/backend/src/graphql/model/Contribution.ts b/backend/src/graphql/model/Contribution.ts
index 1f690a3d8..cf57e632f 100644
--- a/backend/src/graphql/model/Contribution.ts
+++ b/backend/src/graphql/model/Contribution.ts
@@ -5,7 +5,7 @@ import { User } from '@entity/User'
@ObjectType()
export class Contribution {
- constructor(contribution: dbContribution, user: User) {
+ constructor(contribution: dbContribution, user?: User | null) {
this.id = contribution.id
this.firstName = user ? user.firstName : null
this.lastName = user ? user.lastName : null
diff --git a/backend/src/graphql/resolver/AdminResolver.ts b/backend/src/graphql/resolver/AdminResolver.ts
index e9ee0b55b..3435edb94 100644
--- a/backend/src/graphql/resolver/AdminResolver.ts
+++ b/backend/src/graphql/resolver/AdminResolver.ts
@@ -15,6 +15,7 @@ import { AdminCreateContributions } from '@model/AdminCreateContributions'
import { AdminUpdateContribution } from '@model/AdminUpdateContribution'
import { ContributionLink } from '@model/ContributionLink'
import { ContributionLinkList } from '@model/ContributionLinkList'
+import { Contribution } from '@model/Contribution'
import { RIGHTS } from '@/auth/RIGHTS'
import { UserRepository } from '@repository/User'
import AdminCreateContributionArgs from '@arg/AdminCreateContributionArgs'
@@ -23,12 +24,10 @@ import SearchUsersArgs from '@arg/SearchUsersArgs'
import ContributionLinkArgs from '@arg/ContributionLinkArgs'
import { Transaction as DbTransaction } from '@entity/Transaction'
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
-import { Transaction } from '@model/Transaction'
import { TransactionLink, TransactionLinkResult } from '@model/TransactionLink'
import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink'
-import { TransactionRepository } from '@repository/Transaction'
import { calculateDecay } from '@/util/decay'
-import { Contribution } from '@entity/Contribution'
+import { Contribution as DbContribution } from '@entity/Contribution'
import { hasElopageBuys } from '@/util/hasElopageBuys'
import { User as dbUser } from '@entity/User'
import { User } from '@model/User'
@@ -40,7 +39,6 @@ import { Decay } from '@model/Decay'
import Paginated from '@arg/Paginated'
import TransactionLinkFilters from '@arg/TransactionLinkFilters'
import { Order } from '@enum/Order'
-import { communityUser } from '@/util/communityUser'
import { findUserByEmail, activationLink, printTimeDuration } from './UserResolver'
import { sendAccountActivationEmail } from '@/mailer/sendAccountActivationEmail'
import { transactionLinkCode as contributionLinkCode } from './TransactionLinkResolver'
@@ -66,6 +64,7 @@ import { ContributionMessageType } from '@enum/MessageType'
import { ContributionMessage } from '@model/ContributionMessage'
import { sendContributionConfirmedEmail } from '@/mailer/sendContributionConfirmedEmail'
import { sendAddedContributionMessageEmail } from '@/mailer/sendAddedContributionMessageEmail'
+import { ContributionListResult } from '../model/Contribution'
// const EMAIL_OPT_IN_REGISTER = 1
// const EMAIL_OPT_UNKNOWN = 3 // elopage?
@@ -248,7 +247,7 @@ export class AdminResolver {
const creationDateObj = new Date(creationDate)
logger.trace('creationDateObj:', creationDateObj)
validateContribution(creations, amount, creationDateObj)
- const contribution = Contribution.create()
+ const contribution = DbContribution.create()
contribution.userId = emailContact.userId
contribution.amount = amount
contribution.createdAt = new Date()
@@ -259,7 +258,7 @@ export class AdminResolver {
contribution.contributionStatus = ContributionStatus.PENDING
logger.trace('contribution to save', contribution)
- await Contribution.save(contribution)
+ await DbContribution.save(contribution)
return getUserCreation(emailContact.userId)
}
@@ -317,7 +316,7 @@ export class AdminResolver {
const moderator = getUser(context)
- const contributionToUpdate = await Contribution.findOne({
+ const contributionToUpdate = await DbContribution.findOne({
where: { id, confirmedAt: IsNull() },
})
@@ -350,7 +349,7 @@ export class AdminResolver {
contributionToUpdate.moderatorId = moderator.id
contributionToUpdate.contributionStatus = ContributionStatus.PENDING
- await Contribution.save(contributionToUpdate)
+ await DbContribution.save(contributionToUpdate)
const result = new AdminUpdateContribution()
result.amount = amount
result.memo = contributionToUpdate.memo
@@ -367,7 +366,7 @@ export class AdminResolver {
const contributions = await getConnection()
.createQueryBuilder()
.select('c')
- .from(Contribution, 'c')
+ .from(DbContribution, 'c')
.leftJoinAndSelect('c.messages', 'm')
.where({ confirmedAt: IsNull() })
.getMany()
@@ -399,7 +398,7 @@ export class AdminResolver {
@Authorized([RIGHTS.ADMIN_DELETE_CONTRIBUTION])
@Mutation(() => Boolean)
async adminDeleteContribution(@Arg('id', () => Int) id: number): Promise {
- const contribution = await Contribution.findOne(id)
+ const contribution = await DbContribution.findOne(id)
if (!contribution) {
logger.error(`Contribution not found for given id: ${id}`)
throw new Error('Contribution not found for given id.')
@@ -416,7 +415,7 @@ export class AdminResolver {
@Arg('id', () => Int) id: number,
@Ctx() context: Context,
): Promise {
- const contribution = await Contribution.findOne(id)
+ const contribution = await DbContribution.findOne(id)
if (!contribution) {
logger.error(`Contribution not found for given id: ${id}`)
throw new Error('Contribution not found to given id.')
@@ -481,7 +480,7 @@ export class AdminResolver {
contribution.confirmedBy = moderatorUser.id
contribution.transactionId = transaction.id
contribution.contributionStatus = ContributionStatus.CONFIRMED
- await queryRunner.manager.update(Contribution, { id: contribution.id }, contribution)
+ await queryRunner.manager.update(DbContribution, { id: contribution.id }, contribution)
await queryRunner.commitTransaction()
logger.info('creation commited successfuly.')
@@ -506,24 +505,29 @@ export class AdminResolver {
}
@Authorized([RIGHTS.CREATION_TRANSACTION_LIST])
- @Query(() => [Transaction])
+ @Query(() => ContributionListResult)
async creationTransactionList(
@Args()
{ currentPage = 1, pageSize = 25, order = Order.DESC }: Paginated,
@Arg('userId', () => Int) userId: number,
- ): Promise {
+ ): Promise {
const offset = (currentPage - 1) * pageSize
- const transactionRepository = getCustomRepository(TransactionRepository)
- const [userTransactions] = await transactionRepository.findByUserPaged(
- userId,
- pageSize,
- offset,
- order,
- true,
- )
+ const [contributionResult, count] = await getConnection()
+ .createQueryBuilder()
+ .select('c')
+ .from(DbContribution, 'c')
+ .leftJoinAndSelect('c.user', 'u')
+ .where(`user_id = ${userId}`)
+ .limit(pageSize)
+ .offset(offset)
+ .orderBy('c.created_at', order)
+ .getManyAndCount()
- const user = await dbUser.findOneOrFail({ id: userId })
- return userTransactions.map((t) => new Transaction(t, new User(user), communityUser))
+ return new ContributionListResult(
+ count,
+ contributionResult.map((contribution) => new Contribution(contribution, contribution.user)),
+ )
+ // return userTransactions.map((t) => new Transaction(t, new User(user), communityUser))
}
@Authorized([RIGHTS.SEND_ACTIVATION_EMAIL])
@@ -744,7 +748,7 @@ export class AdminResolver {
await queryRunner.startTransaction('REPEATABLE READ')
const contributionMessage = DbContributionMessage.create()
try {
- const contribution = await Contribution.findOne({
+ const contribution = await DbContribution.findOne({
where: { id: contributionId },
relations: ['user'],
})
@@ -773,7 +777,7 @@ export class AdminResolver {
contribution.contributionStatus === ContributionStatus.PENDING
) {
contribution.contributionStatus = ContributionStatus.IN_PROGRESS
- await queryRunner.manager.update(Contribution, { id: contributionId }, contribution)
+ await queryRunner.manager.update(DbContribution, { id: contributionId }, contribution)
}
await sendAddedContributionMessageEmail({
diff --git a/database/migrations/0049-add_user_contacts_table.ts b/database/migrations/0049-add_user_contacts_table.ts
index c3b89ed88..acdd1af61 100644
--- a/database/migrations/0049-add_user_contacts_table.ts
+++ b/database/migrations/0049-add_user_contacts_table.ts
@@ -14,8 +14,8 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
\`type\` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
\`user_id\` int(10) unsigned NOT NULL,
\`email\` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL UNIQUE,
- \`email_verification_code\` bigint(20) unsigned NOT NULL UNIQUE,
- \`email_opt_in_type_id\` int NOT NULL,
+ \`email_verification_code\` bigint(20) unsigned DEFAULT NULL UNIQUE,
+ \`email_opt_in_type_id\` int DEFAULT NULL,
\`email_resend_count\` int DEFAULT '0',
\`email_checked\` tinyint(4) NOT NULL DEFAULT 0,
\`phone\` varchar(255) COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
@@ -41,47 +41,13 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
// merge values from login_email_opt_in table with users.email in new user_contacts table
await queryFn(`
- INSERT INTO user_contacts
- (type, user_id, email, email_verification_code, email_opt_in_type_id, email_resend_count, email_checked, created_at, updated_at, deleted_at)
- SELECT
- 'EMAIL',
- u.id as user_id,
- u.email,
- e.verification_code as email_verification_code,
- e.email_opt_in_type_id,
- e.resend_count as email_resend_count,
- u.email_checked,
- e.created as created_at,
- e.updated as updated_at,
- u.deletedAt as deleted_at\
- FROM
- users as u,
- login_email_opt_in as e
- WHERE
- u.id = e.user_id AND
- e.id in (
- WITH opt_in AS (
- SELECT
- le.id, le.user_id, le.created, le.updated, ROW_NUMBER() OVER (PARTITION BY le.user_id ORDER BY le.created DESC) AS row_num
- FROM
- login_email_opt_in as le
- )
- SELECT
- opt_in.id
- FROM
- opt_in
- WHERE
- row_num = 1);`)
- /*
- // SELECT
- // le.id
- // FROM
- // login_email_opt_in as le
- // WHERE
- // le.user_id = u.id
- // ORDER BY
- // le.updated DESC, le.created DESC LIMIT 1);`)
- */
+ INSERT INTO user_contacts
+ (type, user_id, email, email_verification_code, email_opt_in_type_id, email_resend_count, email_checked, created_at, updated_at, deleted_at)
+ SELECT 'EMAIL', users.id, users.email, optin.verification_code, optin.email_opt_in_type_id, optin.resend_count, users.email_checked, users.created, null, users.deletedAt
+ FROM users LEFT JOIN
+ (SELECT le.id, le.user_id, le.verification_code, le.email_opt_in_type_id, le.resend_count, le.created, le.updated,
+ ROW_NUMBER() OVER (PARTITION BY le.user_id ORDER BY le.created DESC) AS row_num
+ FROM login_email_opt_in as le) AS optin ON users.id = optin.user_id AND row_num = 1;`)
// insert in users table the email_id of the new created email-contacts
const contacts = await queryFn(`SELECT c.id, c.user_id FROM user_contacts as c`)
@@ -113,11 +79,13 @@ export async function downgrade(queryFn: (query: string, values?: any[]) => Prom
)
// reconstruct the previous email back from contacts to users table
- const contacts = await queryFn(`SELECT c.id, c.email, c.user_id FROM user_contacts as c`)
+ const contacts = await queryFn(
+ `SELECT c.id, c.email, c.user_id, c.email_checked FROM user_contacts as c`,
+ )
for (const id in contacts) {
const contact = contacts[id]
await queryFn(
- `UPDATE users SET email = "${contact.email}" WHERE id = "${contact.user_id}" and email_id = "${contact.id}"`,
+ `UPDATE users SET email = "${contact.email}", email_checked="${contact.email_checked}" WHERE id = "${contact.user_id}" and email_id = "${contact.id}"`,
)
}
await queryFn('ALTER TABLE users MODIFY COLUMN email varchar(255) NOT NULL UNIQUE;')
diff --git a/deployment/bare_metal/install.sh b/deployment/bare_metal/install.sh
index ddb2706eb..9e60bec08 100755
--- a/deployment/bare_metal/install.sh
+++ b/deployment/bare_metal/install.sh
@@ -4,6 +4,12 @@
# How to do this is described in detail in [setup.md](./setup.md)
# Find current directory & configure paths
+## For manualy use in terminal
+## set -o allexport
+## SCRIPT_DIR=$(pwd)
+## PROJECT_ROOT=$SCRIPT_DIR/../..
+## set +o allexport
+# Use here in script
set -o allexport
SCRIPT_PATH=$(realpath $0)
SCRIPT_DIR=$(dirname $SCRIPT_PATH)
@@ -90,7 +96,7 @@ sudo certbot
# Install logrotate
sudo apt-get install -y logrotate
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $SCRIPT_DIR/logrotate/gradido.conf.template > $SCRIPT_DIR/logrotate/gradido.conf
-sudo mv $SCRIPT_DIR/logrotate/gradido.conf /etc/logrotate.d/gradido.conf
+sudo cp $SCRIPT_DIR/logrotate/gradido.conf.template /etc/logrotate.d/gradido.conf
sudo chown root:root /etc/logrotate.d/gradido.conf
# Install mysql autobackup
@@ -137,4 +143,4 @@ envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/admin/.env.te
# daily job: 0 4 * * * find /tmp -name "yarn--*" -ctime +1 -exec rm -r {} \; > /dev/null
# Start gradido
# Note: on first startup some errors will occur - nothing serious
-./start.sh
\ No newline at end of file
+./start.sh
diff --git a/deployment/bare_metal/setup.md b/deployment/bare_metal/setup.md
index 652a0a5ce..5892cf4fc 100644
--- a/deployment/bare_metal/setup.md
+++ b/deployment/bare_metal/setup.md
@@ -1,107 +1,233 @@
-# Setup script to setup the server be ready to run gradido
-# This assums you have root access via ssh to your cleanly setup server
-# Furthermore this assumes you have debian (11 64bit) running
-# Check your (Sub-)Domain with your Provider.
-# In this document gddhost.tld refers to your chosen domain
+# Instructions To Run `Gradido` On Your Server
-> ssh root@gddhost.tld
+We split setting up `Gradido` on your server into three steps:
-# change root default shell
-> chsh -s /bin/bash
-# Create user `gradido`
-> useradd -d /home/gradido -m gradido
-> passwd gradido
->> enter new password twice
+- [Preparing your server](#command-list-to-setup-your-server-be-ready-to-install-gradido)
+- [Installing `Gradido`](#use-commands-in-installsh-manually-in-your-shell-for-now)
+- [Crone-Job for `Gradido`](#define-cronjob-to-compensate-yarn-output-in-tmp)
-# Gives the user priviledges - this might be omitted in order to harden security
-# Care: This will require another administering user if you don't want root access.
-# Since this setup expects the user running the software be the same as the administering user,
-# you have to adjust the instructions according to that scenario.
-# You might lock yourself out, if done wrong.
-> usermod -a -G sudo gradido
+## Command List To Setup Your Server Be Ready To Install `Gradido`
-# change gradido default shell
-> chsh -s /bin/bash gradido
-# Install sudo
-> apt-get install sudo
-# switch to the new user
-> su gradido
+We assume you have root access via ssh to your cleanly setup server.
+Furthermore we assume you have debian (11 64bit) running.
-# Register first ssh key for user `gradido`
-> mkdir ~/.ssh
-> chmod 700 ~/.ssh
-> nano ~/.ssh/authorized_keys
->> insert public key
->> ctrl + x
->> save
+Check your (Sub-)Domain with your Provider.
+In this document `gddhost.tld` refers to your chosen domain.
-# Test authentication via SSH
-> ssh -i /path/to/privKey gradido@gddhost.tld
->> This should log you in and allow you to use sudo commands, which will require the user's password
+### SSH into your server
-# Disable password authentication & root login
-> cd /etc/ssh
-> sudo cp sshd_config sshd_config.org
-> sudo nano sshd_config
->> change `PermitRootLogin yes` to `PermitRootLogin no`
->> change `#PasswordAuthentication yes` to `PasswordAuthentication no`
->> change `UsePAM yes` to `UsePAM no`
->> ctrl + x
->> save
-> sudo /etc/init.d/ssh restart
+```bash
+ssh root@gddhost.tld
+```
-# Test SSH Access only, no root ssh access
-> ssh gradido@gddhost.tld
->> Will result in in either a password request for your key or the message `Permission denied (publickey)`
-> ssh -i /path/to/privKey root@gddhost.tld
->> Will result in `Permission denied (publickey)`
-> ssh -i /path/to/privKey gradido@gddhost.tld
->> Will succeed after entering the correct keys password (if any)
+### Change root default shell
-# update system
-> sudo apt-get update
-> sudo apt-get upgrade
+```bash
+chsh -s /bin/bash
+```
-# Install security tools
-## ufw
-> sudo apt-get install ufw
-> sudo ufw allow http
-> sudo ufw allow https
-> sudo ufw allow ssh
-> sudo ufw enable
+### Create user `gradido`
-## fail2ban
-> sudo apt-get install -y fail2ban
-> sudo /etc/init.d/fail2ban restart
+```bash
+$ useradd -d /home/gradido -m gradido
+$ passwd gradido
+# enter new password twice
+```
-# Install gradido
-> sudo apt-get install -y git
-> cd ~
-> git clone https://github.com/gradido/gradido.git
+### Give the user priviledges
-# Timezone
-# Note: This is needed - since there is Summer-Time included in the default server Setup - UTC is REQUIRED for production data
-> sudo timedatectl set-timezone UTC
-# > sudo timedatectl set-ntp on
-# > sudo apt purge ntp
-# > sudo systemctl start systemd-timesyncd
-# >> timedatectl to verify
+This might be omitted in order to harden security.
-# Adjust .env
-# NOTE ';' can not be part of any value
-# The Github Secret is Created on Github in Settimgs -> Webhooks
-> cd gradido/deployment/bare_metal
-> cp .env.dist .env
-> nano .env
->> Adjust values accordingly
-# Define cronjob to compensate yarn output in /tmp
-> yarn creates output in /tmp directory, which must be deleted regularly and will be done per cronjob
-> on stage1 a hourly job is necessary by setting the following job in the crontab for the gradido user
-> crontab -e opens the crontab in edit-mode and insert the following entry:
-> "0 * * * * find /tmp -name "yarn--*" -cmin +60 -exec rm -r {} \; > /dev/null"
-> on stage2 a daily job is necessary by setting the following job in the crontab for the gradido user
-> crontab -e opens the crontab in edit-mode and insert the following entry:
-> "0 4 * * * find /tmp -name "yarn--*" -ctime +1 -exec rm -r {} \; > /dev/null"
-# TODO the install.sh is not yet ready to run directly - consider to use it as pattern to do it manually
-> ./install.sh
+***!!! Attention !!!***
+
+- Care: This will require another administering user if you don't want root access.
+- Since this setup expects the user running the software be the same as the administering user,
+ - you have to adjust the instructions according to that scenario.
+ - you might lock yourself out, if done wrong.
+
+#### Add the new user `gradido` to `sudo` group
+
+```bash
+usermod -a -G sudo gradido
+```
+
+### Change gradido default shell
+
+```bash
+chsh -s /bin/bash gradido
+```
+
+### Install sudo
+
+```bash
+apt-get install sudo
+```
+
+### Switch to the new user
+
+```bash
+su gradido
+```
+
+### Register first ssh key for user `gradido`
+
+```bash
+$ mkdir ~/.ssh
+$ chmod 700 ~/.ssh
+$ nano ~/.ssh/authorized_keys
+# insert public key
+# ctrl + x
+# save
+```
+
+### Test authentication via SSH
+
+If you logout from the server you can test authentication:
+
+```bash
+$ ssh -i /path/to/privKey gradido@gddhost.tld
+# This should log you in and allow you to use sudo commands, which will require the user's password
+```
+
+### Disable password authentication and root login
+
+```bash
+$ cd /etc/ssh
+$ sudo cp sshd_config sshd_config.org
+$ sudo nano sshd_config
+# change 'PermitRootLogin yes' to `PermitRootLogin no`
+# change 'PasswordAuthentication yes' to 'PasswordAuthentication no'
+# change 'UsePAM yes' to 'UsePAM no'
+# ctrl + x
+# save
+$ sudo /etc/init.d/ssh restart
+```
+
+### Test SSH Access only, no root ssh access
+
+```bash
+$ ssh gradido@gddhost.tld
+# Will result in in either a passphrase request for your key or the message 'Permission denied (publickey)'
+$ ssh -i /path/to/privKey root@gddhost.tld
+# Will result in 'Permission denied (publickey)'
+$ ssh -i /path/to/privKey gradido@gddhost.tld
+# Will succeed after entering the correct keys passphrase (if any)
+```
+
+### Update system
+
+```bash
+sudo apt-get update
+sudo apt-get upgrade
+```
+
+### Install security tools
+
+#### Install: `ufw`
+
+```bash
+sudo apt-get install ufw
+sudo ufw allow http
+sudo ufw allow https
+sudo ufw allow ssh
+sudo ufw enable
+```
+
+#### Install: `fail2ban`
+
+```bash
+sudo apt-get install -y fail2ban
+sudo /etc/init.d/fail2ban restart
+```
+
+### Install `Gradido` code
+
+```bash
+sudo apt-get install -y git
+cd ~
+git clone https://github.com/gradido/gradido.git
+```
+
+### Timezone
+
+*Note: This is needed - since there is Summer-Time included in the default server Setup - UTC is REQUIRED for production data.*
+
+```bash
+sudo timedatectl set-timezone UTC
+sudo timedatectl set-ntp on
+sudo apt purge ntp
+sudo systemctl start systemd-timesyncd
+# timedatectl to verify
+```
+
+### Adjust the values in `.env`
+
+***!!! Attention !!!***
+
+*Don't forget this step!
+All your following installations in `install.sh` will fail!*
+
+*Notes:*
+
+- *`;` cannot be part of any value!*
+- *The GitHub secret is created on GitHub in Settings -> Webhooks.*
+
+#### Create `.env` and set values
+
+```bash
+$ cd gradido/deployment/bare_metal
+$ cp .env.dist .env
+$ nano .env
+# adjust values accordingly
+```
+
+## Use Commands In `install.sh` Manually In Your Shell For Now
+
+The script `install.sh` is not yet ready to run directly.
+Use it as pattern to do all steps manually in your terminal shell.
+
+*TODO: Bring the `install.sh` script to run in the shell.*
+
+***!!! Attention !!!***
+
+- *Commands in `install.sh`:*
+ - *The commands for setting the paths in the used env variables are not working directly in the terminal, consider the out commented commands for this purpose.*
+
+Follow the commands in `./install.sh` as installation pattern.
+
+## Define Cronjob To Compensate Yarn Output In `/tmp`
+
+`yarn` creates output in `/tmp` directory, which must be deleted regularly and will be done per Cron-Job.
+
+### On `stage1`
+
+An hourly job is necessary on `stage1` by setting the following job in the `crontab` for the `gradido` user.
+
+Run:
+
+```bash
+crontab -e
+```
+
+This opens the crontab in edit-mode and insert the following entry:
+
+```bash
+0 * * * * find /tmp -name "yarn--*" -cmin +60 -exec rm -r {} \; > /dev/null
+```
+
+### On `stage2`
+
+A daily job is necessary on `stage2` by setting the following job in the `crontab` for the `gradido` user.
+
+Run:
+
+```bash
+crontab -e
+```
+
+This opens the `crontab` in edit-mode and insert the following entry:
+
+```bash
+0 4 * * * find /tmp -name "yarn--*" -ctime +1 -exec rm -r {} \; > /dev/null
+```
diff --git a/e2e-tests/cypress/README.md b/e2e-tests/cypress/README.md
index b1ddae514..4ec1ebe51 100644
--- a/e2e-tests/cypress/README.md
+++ b/e2e-tests/cypress/README.md
@@ -1,24 +1,73 @@
# Gradido End-to-End Testing with [Cypress](https://www.cypress.io/) (CI-ready via Docker)
+A setup to show-case Cypress as an end-to-end testing tool for Gradido running in a Docker container.
+The tests are organized in feature files written in Gherkin syntax.
+
+
+## Features under test
+
+So far these features are initially tested
+- [User authentication](https://github.com/gradido/gradido/blob/master/e2e-tests/cypress/tests/cypress/e2e/User.Authentication.feature)
+- [User profile - change password](https://github.com/gradido/gradido/blob/master/e2e-tests/cypress/tests/cypress/e2e/UserProfile.ChangePassword.feature)
+- [User registration]((https://github.com/gradido/gradido/blob/master/e2e-tests/cypress/tests/cypress/e2e/User.Registration.feature)) (WIP)
-A sample setup to show-case Cypress as an end-to-end testing tool for Gradido running in a Docker container.
-Here we have a simple UI-based happy path login test running against the DEV system.
## Precondition
-Since dependencies and configurations for Github Actions integration is not set up yet, please run in root directory
+
+Before running the tests, change to the repo's root directory (gradido).
+
+### Boot up the system under test
```bash
docker-compose up
```
-to boot up the DEV system, before running the test.
+### Seed the database
+
+The database has to be seeded upfront to every test run.
+
+```bash
+# change to the backend directory
+cd /path/to/gradido/gradido/backend
+
+# install all dependencies
+yarn
+
+# seed the database (everytime before running the tests)
+yarn seed
+```
## Execute the test
+This setup will be integrated in the Gradido Github Actions to automatically support the CI/CD process.
+For now the test setup can only be used locally in two modes.
+
+### Run Cypress directly from the code
+
```bash
+# change to the tests directory
+cd /path/to/gradido/e2e-tests/cypress/tests
+
+# install all dependencies
+yarn install
+
+# a) run the tests on command line
+yarn cypress run
+
+# b) open the Cypress GUI to run the tests in interactive mode
+yarn cypress open
+```
+
+
+### Run Cyprss from a separate Docker container
+
+```bash
+# change to the cypress directory
+cd /path/to/gradido/e2e-tests/cypress/
+
# build a Docker image from the Dockerfile
docker build -t gradido_e2e-tests-cypress .
-# run the Docker container and execute the given tests
-docker run -it --network=host gradido_e2e-tests-cypress yarn run cypress-e2e-tests
+# run the Docker image and execute the given tests
+docker run -it --network=host gradido_e2e-tests-cypress yarn cypress-e2e
```
diff --git a/e2e-tests/cypress/tests/cypress.config.ts b/e2e-tests/cypress/tests/cypress.config.ts
index 815394c5e..ad6a8d7de 100644
--- a/e2e-tests/cypress/tests/cypress.config.ts
+++ b/e2e-tests/cypress/tests/cypress.config.ts
@@ -32,6 +32,7 @@ export default defineConfig({
excludeSpecPattern: "*.js",
baseUrl: "http://localhost:3000",
chromeWebSecurity: false,
+ defaultCommandTimeout: 10000,
supportFile: "cypress/support/index.ts",
viewportHeight: 720,
viewportWidth: 1280,
diff --git a/e2e-tests/cypress/tests/cypress/e2e/User.Registration.feature b/e2e-tests/cypress/tests/cypress/e2e/User.Registration.feature
new file mode 100644
index 000000000..9361d2b84
--- /dev/null
+++ b/e2e-tests/cypress/tests/cypress/e2e/User.Registration.feature
@@ -0,0 +1,13 @@
+Feature: User registration
+ As a user
+ I want to register to create an account
+
+ @skip
+ Scenario: Register successfully
+ Given the browser navigates to page "/register"
+ When the user fills name and email "Regina" "Register" "regina@register.com"
+ And the user agrees to the privacy policy
+ And the user submits the registration form
+ Then the user can use a provided activation link
+ And the user can set a password "Aa12345_"
+ And the user can login with the credentials "regina@register.com" "Aa12345_"
diff --git a/e2e-tests/cypress/tests/cypress/e2e/models/LoginPage.ts b/e2e-tests/cypress/tests/cypress/e2e/models/LoginPage.ts
index a16b93a11..9a0df62ee 100644
--- a/e2e-tests/cypress/tests/cypress/e2e/models/LoginPage.ts
+++ b/e2e-tests/cypress/tests/cypress/e2e/models/LoginPage.ts
@@ -2,8 +2,8 @@
export class LoginPage {
// selectors
- emailInput = "#Email-input-field";
- passwordInput = "#Password-input-field";
+ emailInput = "input[type=email]";
+ passwordInput = "input[type=password]";
submitBtn = "[type=submit]";
emailHint = "#vee_Email";
passwordHint = "#vee_Password";
diff --git a/e2e-tests/cypress/tests/cypress/e2e/models/ProfilePage.ts b/e2e-tests/cypress/tests/cypress/e2e/models/ProfilePage.ts
index b280a0b2a..0532a7ff8 100644
--- a/e2e-tests/cypress/tests/cypress/e2e/models/ProfilePage.ts
+++ b/e2e-tests/cypress/tests/cypress/e2e/models/ProfilePage.ts
@@ -4,8 +4,8 @@ export class ProfilePage {
// selectors
openChangePassword = "[data-test=open-password-change-form]";
oldPasswordInput = "#password-input-field";
- newPasswordInput = "#New-password-input-field";
- newPasswordRepeatInput = "#Repeat-new-password-input-field";
+ newPasswordInput = "#new-password-input-field";
+ newPasswordRepeatInput = "#repeat-new-password-input-field";
submitNewPasswordBtn = "[data-test=submit-new-password-btn]";
goto() {
@@ -19,12 +19,12 @@ export class ProfilePage {
}
enterNewPassword(password: string) {
- cy.get(this.newPasswordInput).clear().type(password);
+ cy.get(this.newPasswordInput).find("input").clear().type(password);
return this;
}
enterRepeatPassword(password: string) {
- cy.get(this.newPasswordRepeatInput).clear().type(password);
+ cy.get(this.newPasswordRepeatInput).find("input").clear().type(password);
return this;
}
diff --git a/e2e-tests/cypress/tests/cypress/e2e/models/RegistrationPage.ts b/e2e-tests/cypress/tests/cypress/e2e/models/RegistrationPage.ts
new file mode 100644
index 000000000..27a9cb8cc
--- /dev/null
+++ b/e2e-tests/cypress/tests/cypress/e2e/models/RegistrationPage.ts
@@ -0,0 +1,42 @@
+///
+
+export class RegistrationPage {
+ // selectors
+ firstnameInput = "#registerFirstname";
+ lastnameInput = "#registerLastname";
+ emailInput = "#Email-input-field";
+ checkbox = "#registerCheckbox";
+ submitBtn = "[type=submit]";
+
+ RegistrationThanxHeadline = ".test-message-headline";
+ RegistrationThanxText = ".test-message-subtitle";
+
+ goto() {
+ cy.visit("/register");
+ return this;
+ }
+
+ enterFirstname(firstname: string) {
+ cy.get(this.firstnameInput).clear().type(firstname);
+ return this;
+ }
+
+ enterLastname(lastname: string) {
+ cy.get(this.lastnameInput).clear().type(lastname);
+ return this;
+ }
+
+ enterEmail(email: string) {
+ cy.get(this.emailInput).clear().type(email);
+ return this;
+ }
+
+ checkPrivacyCheckbox() {
+ cy.get(this.checkbox).click({ force: true });
+ }
+
+ submitRegistrationPage() {
+ cy.get(this.submitBtn).should("be.enabled");
+ cy.get(this.submitBtn).click();
+ }
+}
diff --git a/e2e-tests/cypress/tests/cypress/e2e/models/Toasts.ts b/e2e-tests/cypress/tests/cypress/e2e/models/Toasts.ts
index b2198bc8d..aabd0a45e 100644
--- a/e2e-tests/cypress/tests/cypress/e2e/models/Toasts.ts
+++ b/e2e-tests/cypress/tests/cypress/e2e/models/Toasts.ts
@@ -2,6 +2,9 @@
export class Toasts {
// selectors
+ toastSlot = ".b-toaster-slot";
+ toastTypeSuccess = ".b-toast-success";
+ toastTypeError = ".b-toast-danger";
toastTitle = ".gdd-toaster-title";
toastMessage = ".gdd-toaster-body";
}
diff --git a/e2e-tests/cypress/tests/cypress/support/step_definitions/common_steps.ts b/e2e-tests/cypress/tests/cypress/support/step_definitions/common_steps.ts
index 439974cda..f45358f3c 100644
--- a/e2e-tests/cypress/tests/cypress/support/step_definitions/common_steps.ts
+++ b/e2e-tests/cypress/tests/cypress/support/step_definitions/common_steps.ts
@@ -25,11 +25,11 @@ Then("the user is logged in with username {string}", (username: string) => {
Then("the user cannot login", () => {
const toast = new Toasts();
- cy.get(toast.toastTitle).should("contain.text", "Error!");
- cy.get(toast.toastMessage).should(
- "contain.text",
- "No user with this credentials."
- );
+ cy.get(toast.toastSlot).within(() => {
+ cy.get(toast.toastTypeError);
+ cy.get(toast.toastTitle).should("be.visible");
+ cy.get(toast.toastMessage).should("be.visible");
+ });
});
//
diff --git a/e2e-tests/cypress/tests/cypress/support/step_definitions/user_profile_change_password_steps.ts b/e2e-tests/cypress/tests/cypress/support/step_definitions/user_profile_change_password_steps.ts
index cbe851f02..5396b66bb 100644
--- a/e2e-tests/cypress/tests/cypress/support/step_definitions/user_profile_change_password_steps.ts
+++ b/e2e-tests/cypress/tests/cypress/support/step_definitions/user_profile_change_password_steps.ts
@@ -24,9 +24,9 @@ And("the user submits the password form", () => {
When("the user is presented a {string} message", (type: string) => {
const toast = new Toasts();
- cy.get(toast.toastTitle).should("contain.text", "Success");
- cy.get(toast.toastMessage).should(
- "contain.text",
- "Your password has been changed."
- );
+ cy.get(toast.toastSlot).within(() => {
+ cy.get(toast.toastTypeSuccess);
+ cy.get(toast.toastTitle).should("be.visible");
+ cy.get(toast.toastMessage).should("be.visible");
+ });
});
diff --git a/e2e-tests/cypress/tests/package.json b/e2e-tests/cypress/tests/package.json
index a9979725e..a6f817503 100644
--- a/e2e-tests/cypress/tests/package.json
+++ b/e2e-tests/cypress/tests/package.json
@@ -14,7 +14,7 @@
}
},
"scripts": {
- "cypress": "cypress run",
+ "cypress-e2e": "cypress run",
"lint": "eslint --max-warnings=0 --ext .js,.ts ."
},
"dependencies": {
diff --git a/frontend/src/components/ContributionMessages/ContributionMessagesFormular.vue b/frontend/src/components/ContributionMessages/ContributionMessagesFormular.vue
index e3f9fd5e7..1a5928cc3 100644
--- a/frontend/src/components/ContributionMessages/ContributionMessagesFormular.vue
+++ b/frontend/src/components/ContributionMessages/ContributionMessagesFormular.vue
@@ -7,7 +7,6 @@
v-model="form.text"
:placeholder="$t('form.memo')"
rows="3"
- max-rows="6"
>
diff --git a/frontend/src/components/Contributions/ContributionForm.vue b/frontend/src/components/Contributions/ContributionForm.vue
index 3a9010ec2..47f2be4c4 100644
--- a/frontend/src/components/Contributions/ContributionForm.vue
+++ b/frontend/src/components/Contributions/ContributionForm.vue
@@ -42,7 +42,6 @@
id="contribution-memo"
v-model="form.memo"
rows="3"
- max-rows="6"
:placeholder="$t('contribution.yourActivity')"
required
>
diff --git a/frontend/src/components/GddSend/TransactionForm.vue b/frontend/src/components/GddSend/TransactionForm.vue
index 62361c4d0..69c6b6b45 100644
--- a/frontend/src/components/GddSend/TransactionForm.vue
+++ b/frontend/src/components/GddSend/TransactionForm.vue
@@ -46,7 +46,7 @@
@@ -81,7 +81,11 @@
v-slot="{ errors, valid }"
>
-
+