Merge pull request 'Alpha release 0.1.0' (#9) from dev into main
continuous-integration/drone/tag Build is passing Details
continuous-integration/drone/push Build is passing Details

Reviewed-on: #9
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
This commit is contained in:
Nicolai Ort 2021-03-04 16:02:02 +00:00
commit e60360ccd4
18 changed files with 1049 additions and 96 deletions

View File

@ -1,3 +1,23 @@
---
kind: pipeline
name: tests:node_latest
clone:
disable: true
steps:
- name: checkout pr
image: alpine/git
commands:
- git clone $DRONE_REMOTE_URL .
- git checkout $DRONE_SOURCE_BRANCH
- name: run tests
image: node:latest
commands:
- yarn
- yarn test:ci
trigger:
event:
- pull_request
---
kind: pipeline
type: docker

View File

@ -2,4 +2,4 @@
All notable changes to this project will be documented in this file. Dates are displayed in UTC.
#### 0.0.1
#### 0.1.0

View File

@ -1,3 +1,74 @@
# mailer
# @lfk/mailer
[![Build Status](https://ci.odit.services/api/badges/lfk/mailer/status.svg?ref=refs/heads/main)](https://ci.odit.services/lfk/mailer)
Handles mail generation and sending (pw reset, welcome mail, etc)
Handles mail generation and sending (pw reset, welcome mail, etc)
## Dev Setup 🛠
> Local dev setup
1. Rename the .env.example file to .env (you can adjust app port and other settings, if needed) or generate a example env with `yarn && yarn test:generate_env`.
2. Install Dependencies
```bash
yarn
```
3. Start the server
```bash
yarn dev
```
## Templates
> The mailer uses html and plaintext templates to generate various mails.
> The templates are stored in src/templates by default.
We provide a set of default templates that we use for the ["Lauf für Kaya!" charity run](https://lauf-fuer-kaya.de).
We use handlebars for templateing utilizing i18next for translation - the i18n string format in the templates is : `{{__ "string"}}`
You can provide your own templates by replacing the ones we provided before compiling the project or by simply mounting your custom templates into the docker container.
The server currently needs the following templates to work:
* pw-reset.html
* pw-reset.txt
* test.html
* test.txt
* welcome_runner.html
* welcome_runner.txt
| Name | Type | Default | Description
| - | - | - | -
| APP_PORT | Number | 4010 | The port the backend server listens on. Is optional.
| NODE_ENV | String | dev | The apps env - influences debug info.
| API_KEY | String(min length: 64) | Random generated string | The api key you want to use for auth (query-param `key`), has to be at least 64 chars long.
| API_URL | String(url) | "http://localhost:8080" | The URL ponting to the base (root) of the lfk runner system.
| MAIL_SERVER | String(FQDN) | None | The mailserver (smtp) used to send mails via nodemailer.
| MAIL_PORT | Number | 25 | The mailserver's port (smtp).
| MAIL_USER | String | None | The username used to authenticate against the mailserver.
| MAIL_PASSWORD | String | None | The password used to authenticate against the mailserver.
| MAIL_FROM | String | None | The mail address that mails get sent from.
| PRIVACY_URL | String | "/privacy" | The url path that get's attached to the app url to link to the privacy page.
| IMPRINT_URL | String | "/imprint" | The url path that get's attached to the app url to link to the imprint page.
| COPYRIGHT_OWNER | String | "LfK!" | Text that gets inserted as the "copyright by" owner in the mails.
| EVENT_NAME | String | "Testing 4 Kaya" | The event's name - used to generate the mail text.
| CONTACT_MAIL | String(email) | MAIL_FROM | Contact mail address listed at the bottom of some mail templates.
## Recommended Editor
[Visual Studio Code](https://code.visualstudio.com/)
### Recommended Extensions
* will be automatically recommended via ./vscode/extensions.json
* we also provide a config for i18n-ally in the .vscode folder
## Staging
### Branches & Tags
* vX.Y.Z: Release tags created from the main branch
* The version numbers follow the semver standard
* A new release tag automaticly triggers the release ci pipeline
* main: Protected "release" branch
* The latest tag of the docker image get's build from this
* New releases get created as tags from this
* dev: Current dev branch for merging the different feature branches and bugfixes
* The dev tag of the docker image get's build from this
* Only push minor changes to this branch!
* To merge a feature branch into this please create a pull request
* feature/xyz: Feature branches - nameing scheme: `feature/issueid-title`
* bugfix/xyz: Branches for bugfixes - nameing scheme:`bugfix/issueid-title`

4
jest.config.js Normal file
View File

@ -0,0 +1,4 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};

View File

@ -428,6 +428,35 @@ OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
SOFTWARE
# @types/jest
**Author**: undefined
**Repo**: https://github.com/DefinitelyTyped/DefinitelyTyped.git
**License**: MIT
**Description**: TypeScript definitions for Jest
## License Text
MIT License
Copyright (c) Microsoft Corporation.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE
# @types/node
**Author**: undefined
**Repo**: https://github.com/DefinitelyTyped/DefinitelyTyped.git
@ -486,6 +515,33 @@ OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
SOFTWARE
# axios
**Author**: Matt Zabriskie
**Repo**: https://github.com/axios/axios.git
**License**: MIT
**Description**: Promise based HTTP client for the browser and node.js
## License Text
Copyright (c) 2014-present Matt Zabriskie
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
# cp-cli
**Author**: undefined
**Repo**: git+https://github.com/screendriver/cp-cli.git
@ -515,6 +571,35 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# jest
**Author**: undefined
**Repo**: https://github.com/facebook/jest
**License**: MIT
**Description**: Delightful JavaScript Testing.
## License Text
MIT License
Copyright (c) Facebook, Inc. and its affiliates.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# nodemon
**Author**: [object Object]
**Repo**: https://github.com/remy/nodemon.git
@ -604,6 +689,35 @@ IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
## License Text
# ts-jest
**Author**: Kulshekhar Kabra <kulshekhar@users.noreply.github.com> (https://github.com/kulshekhar)
**Repo**: git+https://github.com/kulshekhar/ts-jest.git
**License**: MIT
**Description**: A preprocessor with source maps support to help use TypeScript with Jest
## License Text
MIT License
Copyright (c) 2016-2018
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# ts-node
**Author**: [object Object]
**Repo**: git://github.com/TypeStrong/ts-node.git

View File

@ -1,84 +1,92 @@
{
"name": "@odit/lfk-mailer",
"version": "0.0.1",
"description": "The document mailer for the LfK! runner system. This generates and sends mails (password reset, welcome, ...)",
"main": "src/app.ts",
"scripts": {
"dev": "nodemon src/app.ts",
"build": "rimraf ./dist && tsc && cp-cli ./src/templates ./dist/templates && cp-cli ./src/locales ./dist/locales",
"licenses:export": "license-exporter --markdown",
"release": "release-it --only-version",
"translations:sort": "node ./scripts/sort_translations.js",
"test:generate_env": "ts-node ./scripts/create_testenv.ts"
},
"repository": {
"type": "git",
"url": "git@git.odit.services:lfk/mailer.git"
},
"keywords": [
"odit",
"lfk",
"mail",
"node"
],
"author": {
"name": "ODIT.Services",
"email": "info@odit.services",
"url": "https://odit.services"
},
"contributors": [
{
"name": "Philipp Dormann",
"email": "philipp@philippdormann.de",
"url": "https://philippdormann.de"
},
{
"name": "Nicolai Ort",
"email": "info@nicolai-ort.com",
"url": "https://nicolai-ort.com"
}
],
"license": "CC-BY-NC-SA-4.0",
"dependencies": {
"@odit/class-validator-jsonschema": "^2.1.1",
"class-transformer": "0.3.1",
"class-validator": "^0.13.1",
"consola": "^2.15.3",
"cors": "^2.8.5",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"handlebars": "^4.7.6",
"i18next": "^19.8.7",
"i18next-fs-backend": "^1.0.8",
"nodemailer": "^6.5.0",
"reflect-metadata": "^0.1.13",
"routing-controllers": "0.9.0-alpha.6",
"routing-controllers-openapi": "2.2.0"
},
"devDependencies": {
"@odit/license-exporter": "^0.0.10",
"@types/express": "^4.17.11",
"@types/node": "^14.14.22",
"@types/nodemailer": "^6.4.0",
"cp-cli": "^2.0.0",
"nodemon": "^2.0.7",
"release-it": "^14.2.2",
"rimraf": "^3.0.2",
"start-server-and-test": "^1.12.0",
"ts-node": "^9.1.1",
"typescript": "^4.1.3"
},
"release-it": {
"git": {
"commit": true,
"requireCleanWorkingDir": false,
"commitMessage": "🚀Bumped version to v${version}",
"requireBranch": "dev",
"push": false,
"tag": false
},
"npm": {
"publish": false
}
}
}
{
"name": "@odit/lfk-mailer",
"version": "0.1.0",
"description": "The document mailer for the LfK! runner system. This generates and sends mails (password reset, welcome, ...)",
"main": "src/app.ts",
"scripts": {
"dev": "nodemon src/app.ts",
"build": "rimraf ./dist && tsc && cp-cli ./src/templates ./dist/templates && cp-cli ./src/locales ./dist/locales",
"licenses:export": "license-exporter --markdown",
"release": "release-it --only-version",
"translations:sort": "node ./scripts/sort_translations.js",
"test": "jest",
"test:watch": "jest --watchAll",
"test:generate_env": "ts-node ./scripts/create_testenv.ts",
"test:ci": "npm run test:generate_env && npm run test:ci:run",
"test:ci:run": "start-server-and-test dev http://localhost:4010/docs/openapi.json test"
},
"repository": {
"type": "git",
"url": "git@git.odit.services:lfk/mailer.git"
},
"keywords": [
"odit",
"lfk",
"mail",
"node"
],
"author": {
"name": "ODIT.Services",
"email": "info@odit.services",
"url": "https://odit.services"
},
"contributors": [
{
"name": "Philipp Dormann",
"email": "philipp@philippdormann.de",
"url": "https://philippdormann.de"
},
{
"name": "Nicolai Ort",
"email": "info@nicolai-ort.com",
"url": "https://nicolai-ort.com"
}
],
"license": "CC-BY-NC-SA-4.0",
"dependencies": {
"@odit/class-validator-jsonschema": "^2.1.1",
"class-transformer": "0.3.1",
"class-validator": "^0.13.1",
"consola": "^2.15.3",
"cors": "^2.8.5",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"handlebars": "^4.7.6",
"i18next": "^19.8.7",
"i18next-fs-backend": "^1.0.8",
"nodemailer": "^6.5.0",
"reflect-metadata": "^0.1.13",
"routing-controllers": "0.9.0-alpha.6",
"routing-controllers-openapi": "2.2.0"
},
"devDependencies": {
"@odit/license-exporter": "^0.0.10",
"@types/express": "^4.17.11",
"@types/jest": "^26.0.20",
"@types/node": "^14.14.22",
"@types/nodemailer": "^6.4.0",
"axios": "^0.21.1",
"cp-cli": "^2.0.0",
"jest": "^26.6.3",
"nodemon": "^2.0.7",
"release-it": "^14.2.2",
"rimraf": "^3.0.2",
"start-server-and-test": "^1.12.0",
"ts-jest": "^26.5.2",
"ts-node": "^9.1.1",
"typescript": "^4.1.3"
},
"release-it": {
"git": {
"commit": true,
"requireCleanWorkingDir": false,
"commitMessage": "🚀Bumped version to v${version}",
"requireBranch": "dev",
"push": false,
"tag": false
},
"npm": {
"publish": false
}
}
}

View File

@ -16,7 +16,7 @@ import { MailServerConfigError } from './errors/MailErrors';
*/
export class Mailer {
private transport: Mail;
private static interpolations = { copyright_owner: config.copyright_owner }
private static interpolations = { copyright_owner: config.copyright_owner, event_name: config.event_name, contact_mail: config.contact_mail }
/**
* Main constructor.
@ -69,11 +69,18 @@ export class Mailer {
public async sendResetMail(to_address: string, token: string, locale: string = "en") {
await i18next.changeLanguage(locale);
const reset_link = `${config.app_url}/reset/${(Buffer.from(token)).toString("base64")}`
const replacements = {
recipient_mail: to_address,
copyright_owner: config.copyright_owner,
link_imprint: `${config.app_url}/imprint`,
link_privacy: `${config.app_url}/privacy`,
reset_link: `${config.app_url}/reset/${(Buffer.from(token)).toString("base64")}`
}
const template_html = Handlebars.compile(fs.readFileSync(__dirname + '/templates/pw-reset.html', { encoding: 'utf8' }));
const template_txt = Handlebars.compile(fs.readFileSync(__dirname + '/templates/pw-reset.txt', { encoding: 'utf8' }));
const body_html = template_html({ recipient_mail: to_address, copyright_owner: config.copyright_owner, link_imprint: `${config.app_url}/imprint`, link_privacy: `${config.app_url}/privacy`, reset_link });
const body_txt = template_txt({ recipient_mail: to_address, copyright_owner: config.copyright_owner, link_imprint: `${config.app_url}/imprint`, link_privacy: `${config.app_url}/privacy`, reset_link });
const body_html = template_html(replacements);
const body_txt = template_txt(replacements);
const mail: MailOptions = {
to: to_address,
@ -91,13 +98,18 @@ export class Mailer {
public async sendTestMail(locale: string = "en") {
await i18next.changeLanguage(locale);
const to_address: string = config.mail_from;
const replacements = {
recipient_mail: to_address,
copyright_owner: config.copyright_owner,
link_imprint: `${config.app_url}/imprint`,
link_privacy: `${config.app_url}/privacy`
}
const template_html = Handlebars.compile(fs.readFileSync(__dirname + '/templates/test.html', { encoding: 'utf8' }));
const template_txt = Handlebars.compile(fs.readFileSync(__dirname + '/templates/test.txt', { encoding: 'utf8' }));
const body_html = template_html({ recipient_mail: to_address, copyright_owner: config.copyright_owner, link_imprint: `${config.app_url}/imprint`, link_privacy: `${config.app_url}/privacy` });
const body_txt = template_txt({ recipient_mail: to_address, copyright_owner: config.copyright_owner, link_imprint: `${config.app_url}/imprint`, link_privacy: `${config.app_url}/privacy` });
const body_html = template_html(replacements);
const body_txt = template_txt(replacements);
fs.writeFileSync("./test.tmp", body_txt);
const mail: MailOptions = {
to: to_address,
subject: i18next.t("test-mail", Mailer.interpolations).toString(),
@ -107,6 +119,39 @@ export class Mailer {
await this.sendMail(mail);
}
/**
* Function for sending a reset mail from the reset mail template.
* @param to_address The address the mail will be sent to. Should always get pulled from a user object.
* @param token The requested password reset token - will be combined with the app_url to generate a password reset link.
*/
public async sendWelcomeMail(to_address: string, token: string, locale: string = "en") {
await i18next.changeLanguage(locale);
const replacements = {
recipient_mail: to_address,
copyright_owner: config.copyright_owner,
link_imprint: `${config.app_url}/imprint`,
link_privacy: `${config.app_url}/privacy`,
selfservice_link: `${config.app_url}/selfservice/profile/${token}`,
forgot_link: `${config.app_url}/selfservice`,
contact_mail: config.contact_mail,
event_name: config.event_name
}
const template_html = Handlebars.compile(fs.readFileSync(__dirname + '/templates/welcome_runner.html', { encoding: 'utf8' }));
const template_txt = Handlebars.compile(fs.readFileSync(__dirname + '/templates/welcome_runner.txt', { encoding: 'utf8' }));
const body_html = template_html(replacements);
const body_txt = template_txt(replacements);
const mail: MailOptions = {
to: to_address,
subject: i18next.t("event_name-registration", Mailer.interpolations).toString(),
text: body_txt,
html: body_html
};
await this.sendMail(mail);
}
/**
* Wrapper function for sending a mail via this object's transporter.
* @param mail MailOptions object containing the

View File

@ -15,7 +15,9 @@ export const config = {
mail_from: process.env.MAIL_FROM,
privacy_url: process.env.PRIVACY_URL || "/privacy",
imprint_url: process.env.IMPRINT_URL || "/imprint",
copyright_owner: process.env.COPYRIGHT_OWNER || "LfK!"
copyright_owner: process.env.COPYRIGHT_OWNER || "LfK!",
event_name: process.env.EVENT_NAME || "Testing 4 Kaya!",
contact_mail: process.env.CONTACT_MAIL || process.env.MAIL_FROM,
}
let errors = 0
if (typeof config.internal_port !== "number") {

View File

@ -4,6 +4,7 @@ import { Mailer } from '../Mailer';
import { locales } from '../models/LocaleEnum';
import { ResetMail } from '../models/ResetMail';
import { SuccessResponse } from '../models/SuccessResponse';
import { WelcomeMail } from '../models/WelcomeMail';
/**
* The mail controller handels all endpoints concerning Mail sending.
@ -45,4 +46,20 @@ export class MailController {
}
return new SuccessResponse(locale);
}
@Post('/registration')
@OpenAPI({ description: "Sends registration welcome mails", parameters: [{ in: "query", name: "locale", schema: { type: "string", enum: ["de", "en"] } }] })
async sendRegistrationWelcome(@Body({ validate: true }) mailOptions: WelcomeMail, @QueryParam("locale") locale: locales) {
if (!this.initialized) {
await this.mailer.init();
this.initialized = true;
}
try {
this.mailer.sendWelcomeMail(mailOptions.address, mailOptions.selfserviceToken, locale?.toString())
} catch (error) {
console.log(error)
throw error;
}
return new SuccessResponse(locale);
}
}

View File

@ -1,7 +1,10 @@
{
"a-password-reset-for-your-account-got-requested": "Ein Passwort Reset wurde für dein Konto beantragt.",
"all-rights-reserved": "Alle Rechte vorbehalten",
"event_name-registration": "{{event_name}} Registrierung",
"if-you-didnt-register-yourself-you-should-contact-us-to-get-your-data-removed-from-our-systems": "Solltest du dich nicht selbst registriert haben schick uns bitte eine Mail und wir entfernen deine Daten aus unserem System: ",
"if-you-didnt-request-the-reset-please-ignore-this-mail": "Solltest du den Reset nicht beantragt haben kannst du diese Mail einfach ignorieren.",
"if-you-ever-loose-the-link-you-can-request-a-new-one-by-visiting-our-website": "Solltest du den Link verlieren kannst du auf unserer Website einen neuen beantragen:",
"imprint": "Impressum",
"lfk-mail-test": "{{copyright_owner}} - Mail test",
"lfk-password-reset": "{{copyright_owner}} - Passwort zurücksetzen",
@ -9,8 +12,15 @@
"privacy": "Datenschutz",
"reset-password": "Passwort zurücksetzen",
"test-mail": "Test mail",
"this-is-a-test-mail-triggered-by-an-admin-in-the-lfk-backend": "Das ist eine Testmail, die von einem Admin im LfK! backend erzeugt wurde.",
"thanks-for-registering-and-welcome-to-the-event_name": "Vielen Dank für die Registrierung und willkommen beim {{event_name}}!",
"the-only-thing-you-have-to-do-now-is-to-bring-your-registration-code-with-you": "Du must nichts weiter machen, außer deinen Registrierungscode zum Lauf mitzubringen.",
"this-is-a-test-mail-triggered-by-an-admin-in-the-lfk-backend": "Das ist eine Testmail, die von einem Admin im LfK! Backend erzeugt wurde.",
"this-mail-was-sent-to-recipient_mail-because-someone-request-a-mail-test-for-this-mail-address": "Du bekommst diese Mail, weil jemand eine Testmail für deine Mail-Adresse angefragt hat.",
"this-mail-was-sent-to-you-because-someone-request-a-password-reset-for-a-account-linked-to-the-mail-address": "Du bekommst diese E-Mail, weil jemand einen Passwort-Reset für deinen Account beantragt hat.",
"this-mail-was-sent-to-you-because-someone-used-your-mail-address-to-register-themselfes-for-the-event_name": "Du bekommst diese Mail, weil jemand deine E-Mail-Adresse verwendet hat, um sich beim {{event_name}} zu registrieren.",
"view-my-data": "Meine Daten",
"we-successfully-processed-your-registration": "Wir haben deine Registrierung erfolgreich verarbeitet.",
"welcome": "Willkommen",
"you-can-view-your-registration-code-lap-times-and-much-more-by-visiting-our-selfservice": "Du kannst deinen Registrierungscode, deine Rundenzeiten unv vieles mehr im Selfservice einsehen:",
"your-password-wont-be-changed-until-you-click-the-reset-link-below-and-set-a-new-one": "Dein Passwort wird erst zurückgesetzt, wenn du den Reset-Link öffnest und ein neues Passwort setzt."
}

View File

@ -1,7 +1,10 @@
{
"a-password-reset-for-your-account-got-requested": "A password reset for your account got requested.",
"all-rights-reserved": "All rights reserved.",
"event_name-registration": "{{event_name}} Registration",
"if-you-didnt-register-yourself-you-should-contact-us-to-get-your-data-removed-from-our-systems": "If you didn't register yourself you should contact us to get your data removed from our systems:",
"if-you-didnt-request-the-reset-please-ignore-this-mail": "If you didn't request the reset please ignore this mail.",
"if-you-ever-loose-the-link-you-can-request-a-new-one-by-visiting-our-website": "If you ever loose the link you can request a new one by visiting our website:",
"imprint": "Imprint",
"lfk-mail-test": "{{copyright_owner}} - Mail test",
"lfk-password-reset": "{{copyright_owner}} - Password reset",
@ -9,8 +12,15 @@
"privacy": "Privacy",
"reset-password": "Reset password",
"test-mail": "Test mail",
"thanks-for-registering-and-welcome-to-the-event_name": "Thanks for registering and welcome to the {{event_name}}!",
"the-only-thing-you-have-to-do-now-is-to-bring-your-registration-code-with-you": "The only thing you have to do now is to bring your registration code with you.",
"this-is-a-test-mail-triggered-by-an-admin-in-the-lfk-backend": "This is a test mail triggered by an admin in the LfK! backend.",
"this-mail-was-sent-to-recipient_mail-because-someone-request-a-mail-test-for-this-mail-address": "This mail was sent to you because someone request a mail test for this mail address.",
"this-mail-was-sent-to-you-because-someone-request-a-password-reset-for-a-account-linked-to-the-mail-address": "This mail was sent to you because someone request a password reset for a account linked to the mail address.",
"this-mail-was-sent-to-you-because-someone-used-your-mail-address-to-register-themselfes-for-the-event_name": "This mail was sent to you, because someone used your mail address to register themselfes for the {{event_name}}",
"view-my-data": "View my data",
"we-successfully-processed-your-registration": "We successfully processed your registration.",
"welcome": "Welcome",
"you-can-view-your-registration-code-lap-times-and-much-more-by-visiting-our-selfservice": "You can view your registration code, lap times and much more by visiting our selfservice:",
"your-password-wont-be-changed-until-you-click-the-reset-link-below-and-set-a-new-one": "Your password won't be changed until you click the reset link below and set a new one."
}

16
src/models/WelcomeMail.ts Normal file
View File

@ -0,0 +1,16 @@
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
/**
* Simple welcome mail request class for validation and easier handling.
*/
export class WelcomeMail {
@IsString()
@IsEmail()
@IsNotEmpty()
address: string;
@IsString()
@IsNotEmpty()
selfserviceToken: string;
}

View File

@ -0,0 +1,397 @@
<!DOCTYPE html>
<html lang="de" xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:v="urn:schemas-microsoft-com:vml">
<head>
<title>{{__ "event_name-registration"}}</title> <!-- The title tag shows in email notifications, like Android 4.4. -->
<meta charset="utf-8"> <!-- utf-8 works for most cases -->
<meta http-equiv="Content-Type" content="text/html charset=UTF-8" />
<meta name="viewport" content="width=device-width"> <!-- Forcing initial-scale shouldn't be necessary -->
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <!-- Use the latest (edge) version of IE rendering engine -->
<meta name="x-apple-disable-message-reformatting"> <!-- Disable auto-scale in iOS 10 Mail entirely -->
<meta name="format-detection" content="telephone=no,address=no,email=no,date=no,url=no"> <!-- Tell iOS not to automatically link certain text strings. -->
<!-- CSS Reset : BEGIN -->
<style>
/* What it does: Remove spaces around the email design added by some email clients. */
/* Beware: It can remove the padding / margin and add a background color to the compose a reply window. */
html,
body {
margin: 0 auto !important;
padding: 0 !important;
height: 100% !important;
width: 100% !important;
}
/* What it does: Stops email clients resizing small text. */
* {
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
/* What it does: Centers email on Android 4.4 */
div[style*="margin: 16px 0"] {
margin:0 !important;
}
/* What it does: Stops Outlook from adding extra spacing to tables. */
table,
td {
mso-table-lspace: 0pt !important;
mso-table-rspace: 0pt !important;
}
/* What it does: Fixes webkit padding issue. */
table {
border: 0;
border-spacing: 0;
border-collapse: collapse
}
/* What it does: Forces Samsung Android mail clients to use the entire viewport. */
#MessageViewBody,
#MessageWebViewDiv{
width: 100% !important;
}
/* What it does: Uses a better rendering method when resizing images in IE. */
img {
-ms-interpolation-mode:bicubic;
}
/* What it does: Prevents Windows 10 Mail from underlining links despite inline CSS. Styles for underlined links should be inline. */
a {
text-decoration: none;
}
/* What it does: A work-around for email clients automatically linking certain text strings. */
/* iOS */
a[x-apple-data-detectors],
.unstyle-auto-detected-links a,
.aBn {
border-bottom: 0 !important;
cursor: default !important;
color: inherit !important;
text-decoration: none !important;
font-size: inherit !important;
font-family: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
}
u + #body a, /* Gmail */
#MessageViewBody a /* Samsung Mail */
{
color: inherit;
text-decoration: none;
font-size: inherit;
font-family: inherit;
font-weight: inherit;
line-height: inherit;
}
/* What it does: Prevents Gmail from changing the text color in conversation threads. */
.im {
color: inherit !important;
}
/* What it does: Prevents Gmail from displaying an download button on large, non-linked images. */
.a6S {
display: none !important;
opacity: 0.01 !important;
}
/* If the above doesn't work, add a .g-img class to any image in question. */
img.g-img + div {
display:none !important;
}
/* What it does: Removes right gutter in Gmail iOS app: https://github.com/TedGoas/Cerberus/issues/89 */
/* Create one of these media queries for each additional viewport size you'd like to fix */
/* iPhone 4, 4S, 5, 5S, 5C, and 5SE */
@media only screen and (min-device-width: 320px) and (max-device-width: 374px) {
u ~ div .email-container {
min-width: 320px !important;
}
}
/* iPhone 6, 6S, 7, 8, and X */
@media only screen and (min-device-width: 375px) and (max-device-width: 413px) {
u ~ div .email-container {
min-width: 375px !important;
}
}
/* iPhone 6+, 7+, and 8+ */
@media only screen and (min-device-width: 414px) {
u ~ div .email-container {
min-width: 414px !important;
}
}
</style>
<!-- What it does: Helps DPI scaling in Outlook 2007-2013 -->
<!--[if gte mso 9]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
<!-- CSS Reset : END -->
<!-- Progressive Enhancements : BEGIN -->
<style>
/* What it does: Hover styles for buttons and tags */
.s-btn__primary:hover {
background: #0077CC !important;
border-color: #0077CC !important;
}
.s-btn__white:hover {
background: #EFF0F1 !important;
border-color: #EFF0F1 !important;
}
.s-btn__outlined:hover {
background: rgba(0,119,204,.05) !important;
color: #005999 !important;
}
.s-tag:hover,
.post-tag:hover {
border-color: #cee0ed !important;
background: #cee0ed !important;
}
/* What it does: Styles markdown links that we can't write inline CSS for. */
.has-markdown a,
.has-markdown a:visited {
color: #0077CC !important;
text-decoration: none !important;
}
/* What it does: Styles markdown code blocks that we can't write inline CSS for. */
code {
padding: 1px 5px;
background-color: #EFF0F1;
color: #242729;
font-size: 13px;
line-height: inherit;
font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, sans-serif;
}
pre {
margin: 0 0 15px;
line-height: 17px;
background-color: #EFF0F1;
padding: 4px 8px;
border-radius: 3px;
overflow-x: auto;
}
pre code {
margin: 0 0 15px;
padding: 0;
line-height: 17px;
background-color: none;
}
/* What it does: Styles markdown blockquotes that we can't write inline CSS for. */
blockquote {
margin: 0 0 15px;
padding: 4px 10px;
background-color: #FFF8DC;
border-left: 2px solid #ffeb8e;
}
blockquote p {
padding: 4px 0;
margin: 0;
overflow-wrap: break-word;
}
/* What it does: Rounds corners in email clients that support it */
.bar {
border-radius: 5px;
}
.btr {
border-top-left-radius: 5px;
border-top-right-radius: 5px;
}
.bbr {
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
}
@media screen and (max-width: 680px) {
/* What it does: Forces table cells into full-width rows. */
.stack-column,
.stack-column-center {
display: block !important;
width: 100% !important;
max-width: 100% !important;
direction: ltr !important;
}
/* And center justify these ones. */
.stack-column-center {
text-align: center !important;
}
/* Hides things in small viewports. */
.hide-on-mobile {
display: none !important;
max-height: 0 !important;
overflow: hidden !important;
visibility: hidden !important;
}
/* What it does: Utility classes to reduce spacing for smaller viewports. */
.sm-p-none {padding: 0 !important;}
.sm-pt-none {padding-top: 0 !important;}
.sm-pb-none {padding-bottom: 0 !important;}
.sm-pr-none {padding-right: 0 !important;}
.sm-pl-none {padding-left: 0 !important;}
.sm-px-none {padding-left: 0 !important; padding-right: 0 !important;}
.sm-py-none {padding-top: 0 !important; padding-bottom: 0 !important;}
.sm-p {padding: 20px !important;}
.sm-pt {padding-top: 20px !important;}
.sm-pb {padding-bottom: 20px !important;}
.sm-pr {padding-right: 20px !important;}
.sm-pl {padding-left: 20px !important;}
.sm-px {padding-left: 20px !important; padding-right: 20px !important;}
.sm-py {padding-top: 20px !important; padding-bottom: 20px !important;}
.sm-mb {margin-bottom: 20px !important;}
/* What it does: Utility classes to kill border radius for smaller viewports. Used mainly on the email's main container(s). */
.bar,
.btr,
.bbr {
border-top-left-radius: 0;
border-top-right-radius: 0;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
}
</style>
<!-- Progressive Enhancements : END -->
</head>
<!--
The email background color is defined in three places, just below. If you change one, remember to change the others.
1. body tag: for most email clients
2. center tag: for Gmail and Inbox mobile apps and web versions of Gmail, GSuite, Inbox, Yahoo, AOL, Libero, Comcast, freenet, Mail.ru, Orange.fr
3. mso conditional: For Windows 10 Mail
-->
<body width="100%" style="margin: 0; padding: 0 !important; background: #f3f3f5; mso-line-height-rule: exactly;">
<center style="width: 100%; background: #f3f3f5;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%" style="background-color: #f3f3f5;">
<tr>
<td>
<![endif]-->
<!-- Visually Hidden Preview Text : BEGIN -->
<div style="display: none; font-size: 1px; line-height: 1px; max-height: 0px; max-width: 0px; opacity: 0; overflow: hidden; mso-hide: all; font-family: sans-serif;">
{{__ "event_name-registration"}}
</div>
<!-- Visually Hidden Preview Text : END -->
<div class="email-container" style="max-width: 680px; margin: 0 auto;">
<!--[if mso]>
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="680" align="center">
<tr>
<td>
<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="max-width: 680px; width:100%">
<tr>
<td style="padding: 30px; background-color: #ffffff;" class="sm-p bar">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
<tr>
<td style="padding-bottom: 15px; font-family: arial, sans-serif; font-size: 15px; line-height: 21px; color: #3C3F44; text-align: left;">
<h1 style="font-weight: bold; font-size: 27px; line-height: 27px; color: #0C0D0E; margin: 0 0 15px 0;">{{event_name}}</h1>
</td>
</tr>
<tr>
<td style="padding-bottom: 15px; font-family: arial, sans-serif; font-size: 15px; line-height: 21px; color: #3C3F44; text-align: left;">
<h1 style="font-weight: bold; font-size: 21px; line-height: 21px; color: #0C0D0E; margin: 0 0 15px 0;">{{__ "welcome"}}</h1>
<p style="margin: 0 0 15px;" class="has-markdown">
{{__ "thanks-for-registering-and-welcome-to-the-event_name"}} <br>
{{__ "we-successfully-processed-your-registration"}}<br><br>
{{__ "the-only-thing-you-have-to-do-now-is-to-bring-your-registration-code-with-you"}}<br>
{{__ "you-can-view-your-registration-code-lap-times-and-much-more-by-visiting-our-selfservice"}}
</p>
</td>
</tr>
<!-- Button Row : BEGIN -->
<tr>
<td>
<!-- Button : BEGIN -->
<table align="left" border="0" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="s-btn s-btn__primary" style="border-radius: 4px; background: #0095ff;">
<a class="s-btn s-btn__primary" href="{{selfservice_link}}" target="_parent" style="background: #0095FF; border: 1px solid #0077cc; box-shadow: inset 0 1px 0 0 rgba(102,191,255,.75); font-family: arial, sans-serif; font-size: 17px; line-height: 17px; color: #ffffff; text-align: center; text-decoration: none; padding: 13px 17px; display: block; border-radius: 4px; white-space: nowrap;">{{__ "view-my-data"}}</a>
</td>
</tr>
</table>
<!-- Button : END -->
</td>
</tr>
<tr>
<td style="padding-bottom: 15px; font-family: arial, sans-serif; font-size: 15px; line-height: 21px; color: #3C3F44; text-align: left;">
<p>Link: <a href="{{selfservice_link}}">{{selfservice_link}}</a><br>
{{__ "if-you-ever-loose-the-link-you-can-request-a-new-one-by-visiting-our-website"}} <a href="{{forgot_link}}">{{forgot_link}}</a></p></td>
</tr>
<!-- Button Row : END -->
</table>
</td>
</tr>
<!-----------------------------
EMAIL BODY : END
------------------------------>
<!-- Footer : BEGIN -->
<tr>
<td style="padding: 30px;" class="sm-p">
<table align="left" border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%">
<!-- Subscription Info : BEGIN -->
<tr>
<td style="padding-bottom: 10px; font-size: 12px; line-height: 15px; font-family: arial, sans-serif; color: #9199A1; text-align: left;">
Copyright © {{copyright_owner}}. {{__ "all-rights-reserved"}}
</td>
</tr>
<tr>
<td style="font-size: 12px; line-height: 15px; font-family: arial, sans-serif; color: #9199A1; text-align: left;">
<a href="{{link_imprint}}"
style="color: #9199A1; text-decoration: underline;">{{__ "imprint"}}</a>&nbsp;&nbsp;&nbsp;&nbsp;
<a href="{{link_privacy}}" style="color: #9199A1; text-decoration: underline;">{{__ "privacy"}}</a>
</td>
</tr>
<!-- Subscription Info : BEGIN -->
<!-- HR line : BEGIN -->
<tr>
<td style="padding: 30px 0;" width="100%" class="sm-py">
<table aria-hidden="true" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%">
<tr>
<td height="1" width="100%" style="font-size: 0; line-height: 0; border-top: 1px solid #D6D8DB;">&nbsp;</td>
</tr>
</table>
</td>
</tr>
<!-- HR line : END -->
<tr>
<td style="padding-bottom: 5px; font-size: 12px; line-height: 15px; font-family: arial, sans-serif; color: #9199A1; text-align: left;">
{{__ "this-mail-was-sent-to-you-because-someone-used-your-mail-address-to-register-themselfes-for-the-event_name"}}.<br>
{{__ "if-you-didnt-register-yourself-you-should-contact-us-to-get-your-data-removed-from-our-systems"}} <a href="mailto:{{contact_mail}}">{{contact_mail}}</a>
</td>
</tr>
<!-- Sender Info : END -->
</table>
</td>
</tr>
<!-- Footer : END -->
</table>
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</center>
</body>
</html>

View File

@ -0,0 +1,17 @@
{{__ "event_name-registration"}}
{{__ "thanks-for-registering-and-welcome-to-the-event_name"}}
{{__ "we-successfully-processed-your-registration"}}
{{__ "the-only-thing-you-have-to-do-now-is-to-bring-your-registration-code-with-you"}}
{{__ "you-can-view-your-registration-code-lap-times-and-much-more-by-visiting-our-selfservice"}}
{{selfservice_link}}
{{__ "if-you-ever-loose-the-link-you-can-request-a-new-one-by-visiting-our-website"}} {{forgot_link}}
Copyright © {{copyright_owner}}. {{__ "all-rights-reserved"}}.
{{__ "imprint"}}: {{link_imprint}} | {{__ "privacy"}}: {{link_privacy}}
{{__ "this-mail-was-sent-to-you-because-someone-used-your-mail-address-to-register-themselfes-for-the-event_name"}}
{{__ "if-you-didnt-register-yourself-you-should-contact-us-to-get-your-data-removed-from-our-systems"}} {{contact_mail}}

View File

@ -0,0 +1,34 @@
import axios from 'axios';
import { config } from '../config';
const base = "http://localhost:" + config.internal_port
describe('GET /docs/openapi.json', () => {
it('OpenAPI Spec is availdable 200', async () => {
const res = await axios.get(base + '/docs/openapi.json');
expect(res.status).toEqual(200);
});
});
describe('GET /docs/swagger.json', () => {
it('OpenAPI Spec is availdable 200', async () => {
const res = await axios.get(base + '/docs/swagger.json');
expect(res.status).toEqual(200);
});
});
describe('GET /docs/swaggerui', () => {
it('swaggerui is availdable 200', async () => {
const res = await axios.get(base + '/docs/swaggerui');
expect(res.status).toEqual(200);
});
});
describe('GET /docs/redoc', () => {
it('redoc is availdable 200', async () => {
const res = await axios.get(base + '/docs/redoc');
expect(res.status).toEqual(200);
});
});
describe('GET /docs/rapidoc', () => {
it('rapidoc is availdable 200', async () => {
const res = await axios.get(base + '/docs/rapidoc');
expect(res.status).toEqual(200);
});
});

View File

@ -0,0 +1,72 @@
import axios from 'axios';
import { config } from '../config';
const base = "http://localhost:" + config.internal_port
const axios_config = {
validateStatus: undefined
};
describe('POST /reset without auth', () => {
it('Post without auth should return 401', async () => {
const res = await axios.post(base + '/reset', null, axios_config);
expect(res.status).toEqual(401);
});
});
describe('POST /reset with auth but wrong body', () => {
it('Post with auth but no body should return 400', async () => {
const res = await axios.post(base + '/reset?key=' + config.api_key, null, axios_config);
expect(res.status).toEqual(400);
});
it('Post with auth but no mail should return 400', async () => {
const res = await axios.post(base + '/reset?key=' + config.api_key, { resetKey: "test" }, axios_config);
expect(res.status).toEqual(400);
});
it('Post with auth but no reset key should return 400', async () => {
const res = await axios.post(base + '/reset?key=' + config.api_key, { address: "test@dev.lauf-fuer-kaya.de" }, axios_config);
expect(res.status).toEqual(400);
});
it('Post with auth but invalid mail should return 400', async () => {
const res = await axios.post(base + '/reset?key=' + config.api_key, { resetKey: "test", address: "testdev.l.de" }, axios_config);
expect(res.status).toEqual(400);
});
});
describe('POST /reset with auth and vaild body', () => {
it('Post with auth, body and no locale should return 200', async () => {
const res = await axios.post(base + '/reset?key=' + config.api_key, {
resetKey: "test",
address: "test@dev.lauf-fuer-kaya.de"
}, axios_config);
expect(res.status).toEqual(200);
expect(res.data).toEqual({
success: true,
message: "Sent!",
locale: "en"
})
});
it('Post with auth, body and locale=en should return 200', async () => {
const res = await axios.post(base + '/reset?locale=en&key=' + config.api_key, {
resetKey: "test",
address: "test@dev.lauf-fuer-kaya.de"
}, axios_config);
expect(res.status).toEqual(200);
expect(res.data).toEqual({
success: true,
message: "Sent!",
locale: "en"
})
});
it('Post with auth, body and locale=de should return 200', async () => {
const res = await axios.post(base + '/reset?locale=de&key=' + config.api_key, {
resetKey: "test",
address: "test@dev.lauf-fuer-kaya.de"
}, axios_config);
expect(res.status).toEqual(200);
expect(res.data).toEqual({
success: true,
message: "Sent!",
locale: "de"
})
});
});

View File

@ -0,0 +1,72 @@
import axios from 'axios';
import { config } from '../config';
const base = "http://localhost:" + config.internal_port
const axios_config = {
validateStatus: undefined
};
describe('POST /registration without auth', () => {
it('Post without auth should return 401', async () => {
const res = await axios.post(base + '/registration', null, axios_config);
expect(res.status).toEqual(401);
});
});
describe('POST /registration with auth but wrong body', () => {
it('Post with auth but no body should return 400', async () => {
const res = await axios.post(base + '/registration?key=' + config.api_key, null, axios_config);
expect(res.status).toEqual(400);
});
it('Post with auth but no mail should return 400', async () => {
const res = await axios.post(base + '/registration?key=' + config.api_key, { selfserviceToken: "test" }, axios_config);
expect(res.status).toEqual(400);
});
it('Post with auth but no reset key should return 400', async () => {
const res = await axios.post(base + '/registration?key=' + config.api_key, { address: "test@dev.lauf-fuer-kaya.de" }, axios_config);
expect(res.status).toEqual(400);
});
it('Post with auth but invalid mail should return 400', async () => {
const res = await axios.post(base + '/registration?key=' + config.api_key, { selfserviceToken: "test", address: "testdev.l.de" }, axios_config);
expect(res.status).toEqual(400);
});
});
describe('POST /reset with auth and vaild body', () => {
it('Post with auth, body and no locale should return 200', async () => {
const res = await axios.post(base + '/registration?key=' + config.api_key, {
selfserviceToken: "test",
address: "test@dev.lauf-fuer-kaya.de"
}, axios_config);
expect(res.status).toEqual(200);
expect(res.data).toEqual({
success: true,
message: "Sent!",
locale: "en"
})
});
it('Post with auth, body and locale=en should return 200', async () => {
const res = await axios.post(base + '/registration?locale=en&key=' + config.api_key, {
selfserviceToken: "test",
address: "test@dev.lauf-fuer-kaya.de"
}, axios_config);
expect(res.status).toEqual(200);
expect(res.data).toEqual({
success: true,
message: "Sent!",
locale: "en"
})
});
it('Post with auth, body and locale=de should return 200', async () => {
const res = await axios.post(base + '/registration?locale=de&key=' + config.api_key, {
selfserviceToken: "test",
address: "test@dev.lauf-fuer-kaya.de"
}, axios_config);
expect(res.status).toEqual(200);
expect(res.data).toEqual({
success: true,
message: "Sent!",
locale: "de"
})
});
});

View File

@ -0,0 +1,44 @@
import axios from 'axios';
import { config } from '../config';
const base = "http://localhost:" + config.internal_port
const axios_config = {
validateStatus: undefined
};
describe('POST /test without auth', () => {
it('Post without auth should return 401', async () => {
const res = await axios.post(base + '/test', null, axios_config);
expect(res.status).toEqual(401);
});
});
describe('POST /test with auth', () => {
it('Post with auth and no locale should return 200', async () => {
const res = await axios.post(base + '/test?key=' + config.api_key, null, axios_config);
expect(res.status).toEqual(200);
expect(res.data).toEqual({
success: true,
message: "Sent!",
locale: "en"
})
});
it('Post with auth and locale=en should return 200', async () => {
const res = await axios.post(base + '/test?locale=en&key=' + config.api_key, null, axios_config);
expect(res.status).toEqual(200);
expect(res.data).toEqual({
success: true,
message: "Sent!",
locale: "en"
})
});
it('Post with auth and locale=de should return 200', async () => {
const res = await axios.post(base + '/test?locale=de&key=' + config.api_key, null, axios_config);
expect(res.status).toEqual(200);
expect(res.data).toEqual({
success: true,
message: "Sent!",
locale: "de"
})
});
});