feat: design applications and first api calls

Co-authored-by: Walid <87608619+WalidKorchi@users.noreply.github.com>
This commit is contained in:
Divlo
2021-10-24 06:09:43 +02:00
parent 33bd2bb6bf
commit a0fa66e8f5
136 changed files with 14787 additions and 1668 deletions

View File

@ -0,0 +1,18 @@
import { getUsersCurrentHandler } from './users/current/get'
import { postUsersRefreshTokenHandler } from './users/refresh-token/post'
export interface Handler {
method: 'GET' | 'POST' | 'PUT' | 'DELETE'
url: string
response: {
body: any
statusCode: number
}
}
export type Handlers = Handler[]
export const authenticationHandlers = [
getUsersCurrentHandler,
postUsersRefreshTokenHandler
]

View File

@ -0,0 +1,19 @@
import { Handler } from '../../handler'
import { user, userSettings } from '../user'
export const getUsersCurrentHandler: Handler = {
method: 'GET',
url: '/users/current',
response: {
statusCode: 200,
body: {
user: {
...user,
settings: userSettings,
currentStrategy: 'local',
strategies: ['local']
}
}
}
}

View File

@ -0,0 +1,14 @@
import { Handler } from '../../handler'
export const postUsersRefreshTokenHandler: Handler = {
method: 'POST',
url: '/users/refresh-token',
response: {
statusCode: 200,
body: {
accessToken: 'access-token',
expiresIn: 3600000,
type: 'Bearer'
}
}
}

View File

@ -0,0 +1,10 @@
import { Handler } from '../../handler'
export const postUsersResetPasswordHandler: Handler = {
method: 'POST',
url: '/users/reset-password',
response: {
statusCode: 200,
body: 'Password-reset request successful, please check your emails!'
}
}

View File

@ -0,0 +1,23 @@
import { Handler } from '../../handler'
export const putUsersResetPasswordHandler: Handler = {
method: 'PUT',
url: '/users/reset-password',
response: {
statusCode: 200,
body: 'The new password has been saved!'
}
}
export const putUsersResetPasswordInvalidTemporaryTokenHandler: Handler = {
method: 'PUT',
url: '/users/reset-password',
response: {
statusCode: 400,
body: {
statusCode: 400,
error: 'Bad Request',
message: '"tempToken" is invalid'
}
}
}

View File

@ -0,0 +1,28 @@
import { Handler } from '../../handler'
export const postUsersSigninHandler: Handler = {
method: 'POST',
url: '/users/signin',
response: {
statusCode: 200,
body: {
accessToken: 'access-token',
refreshToken: 'refresh-token',
expiresIn: 3600000,
type: 'Bearer'
}
}
}
export const postUsersSigninInvalidCredentialsHandler: Handler = {
method: 'POST',
url: '/users/signin',
response: {
statusCode: 400,
body: {
statusCode: 400,
error: 'Bad Request',
message: 'Invalid credentials.'
}
}
}

View File

@ -0,0 +1,30 @@
import { Handler } from '../../handler'
import { user, userSettings } from '../user'
export const postUsersSignupHandler: Handler = {
method: 'POST',
url: '/users/signup',
response: {
statusCode: 201,
body: {
user: {
...user,
settings: userSettings
}
}
}
}
export const postUsersSignupAlreadyUsedHandler: Handler = {
method: 'POST',
url: '/users/signup',
response: {
statusCode: 400,
body: {
statusCode: 400,
error: 'Bad Request',
message: 'body.email or body.name already taken.'
}
}
}

View File

@ -0,0 +1,26 @@
import { UserSettings } from '../../../models/UserSettings'
import { UserPublic } from '../../../models/User'
export const user: UserPublic = {
id: 1,
name: 'Divlo',
email: 'contact@divlo.fr',
logo: undefined,
status: undefined,
biography: undefined,
website: 'https://divlo.fr',
isConfirmed: true,
createdAt: '2021-10-20T20:30:51.595Z',
updatedAt: '2021-10-20T20:59:08.485Z'
}
export const userSettings: UserSettings = {
id: 1,
language: 'en',
theme: 'dark',
isPublicEmail: false,
isPublicGuilds: false,
createdAt: '2021-10-20T20:30:51.605Z',
updatedAt: '2021-10-22T07:22:07.956Z',
userId: 1
}

View File

@ -0,0 +1,17 @@
import { authenticationHandlers } from '../../../../fixtures/handler'
describe('Pages > /application/[guildId]/[channelId]', () => {
beforeEach(() => {
cy.task('stopMockServer')
})
it('should redirect the user to `/application` if `guildId` or `channelId` are not numbers', () => {
cy.task('startMockServer', authenticationHandlers).setCookie(
'refreshToken',
'refresh-token'
)
cy.visit('/application/abc/abc')
.location('pathname')
.should('eq', '/application')
})
})

View File

@ -0,0 +1,35 @@
import { authenticationHandlers } from '../../../fixtures/handler'
const applicationPaths = [
'/application',
'/application/users/0',
'/application/guilds/create',
'/application/guilds/join',
'/application/0/0'
]
describe('Pages > /application', () => {
beforeEach(() => {
cy.task('stopMockServer')
})
it('should redirect the user to `/authentication/signin` if not signed in', () => {
for (const applicationPath of applicationPaths) {
cy.visit(applicationPath)
.location('pathname')
.should('eq', '/authentication/signin')
}
})
it('should not redirect the user if signed in', () => {
cy.task('startMockServer', authenticationHandlers).setCookie(
'refreshToken',
'refresh-token'
)
for (const applicationPath of applicationPaths) {
cy.visit(applicationPath)
.location('pathname')
.should('eq', applicationPath)
}
})
})

View File

@ -0,0 +1,37 @@
import { postUsersResetPasswordHandler } from '../../../fixtures/users/reset-password/post'
import { user } from '../../../fixtures/users/user'
describe('Pages > /authentication/forgot-password', () => {
beforeEach(() => {
cy.task('stopMockServer')
cy.visit('/authentication/forgot-password')
})
it('should succeeds and sends a password-reset request', () => {
cy.task('startMockServer', [postUsersResetPasswordHandler])
cy.get('#message').should('not.exist')
cy.get('[data-cy=input-email]').type(user.email)
cy.get('[data-cy=submit]').click()
cy.get('#message').should(
'have.text',
'Success: Password-reset request successful, please check your emails!'
)
})
it('should fails with unreachable api server', () => {
cy.get('#message').should('not.exist')
cy.get('[data-cy=input-email]').type(user.email)
cy.get('[data-cy=submit]').click()
cy.get('#message').should('have.text', 'Error: Internal Server Error.')
})
it('should fails with wrong email format', () => {
cy.get('#message').should('not.exist')
cy.get('[data-cy=input-email]').type('test')
cy.get('[data-cy=submit]').click()
cy.get('#message').should(
'have.text',
'Error: Mmm… It seems that this email is not valid 🤔.'
)
})
})

View File

@ -0,0 +1,45 @@
import {
putUsersResetPasswordHandler,
putUsersResetPasswordInvalidTemporaryTokenHandler
} from '../../../fixtures/users/reset-password/put'
describe('Pages > /authentication/reset-password', () => {
beforeEach(() => {
cy.task('stopMockServer')
})
it('should succeeds and redirect user to sign in page', () => {
cy.task('startMockServer', [putUsersResetPasswordHandler])
cy.visit('/authentication/reset-password?temporaryToken=abcdefg')
cy.get('#message').should('not.exist')
cy.get('[data-cy=input-password]').type('somepassword')
cy.get('[data-cy=submit]').click()
cy.location('pathname').should('eq', '/authentication/signin')
})
it('should fails with invalid `temporaryToken`', () => {
cy.task('startMockServer', [
putUsersResetPasswordInvalidTemporaryTokenHandler
])
cy.visit('/authentication/reset-password')
cy.get('#message').should('not.exist')
cy.get('[data-cy=input-password]').type('somepassword')
cy.get('[data-cy=submit]').click()
cy.get('#message').should('have.text', 'Error: Invalid value.')
})
it('should fails with unreachable api server', () => {
cy.visit('/authentication/reset-password')
cy.get('#message').should('not.exist')
cy.get('[data-cy=input-password]').type('randompassword')
cy.get('[data-cy=submit]').click()
cy.get('#message').should('have.text', 'Error: Internal Server Error.')
})
it('should fails with empty password value', () => {
cy.visit('/authentication/reset-password')
cy.get('#message').should('not.exist')
cy.get('[data-cy=submit]').click()
cy.get('#message').should('have.text', 'Error: Invalid value.')
})
})

View File

@ -0,0 +1,55 @@
import { authenticationHandlers } from '../../../fixtures/handler'
import {
postUsersSigninHandler,
postUsersSigninInvalidCredentialsHandler
} from 'cypress/fixtures/users/signin/post'
import { user } from '../../../fixtures/users/user'
describe('Pages > /authentication/signin', () => {
beforeEach(() => {
cy.task('stopMockServer')
cy.visit('/authentication/signin')
})
it('should succeeds and sign in the user', () => {
cy.task('startMockServer', [
...authenticationHandlers,
postUsersSigninHandler
])
cy.get('#error-email').should('not.exist')
cy.get('#error-password').should('not.exist')
cy.get('[data-cy=input-email]').type(user.email)
cy.get('[data-cy=input-password]').type('randompassword')
cy.get('[data-cy=submit]').click()
cy.location('pathname').should('eq', '/application')
})
it('should fails with unreachable api server', () => {
cy.get('#error-email').should('not.exist')
cy.get('#error-password').should('not.exist')
cy.get('[data-cy=input-email]').type(user.email)
cy.get('[data-cy=input-password]').type('randompassword')
cy.get('[data-cy=submit]').click()
cy.get('#message').should('have.text', 'Error: Internal Server Error.')
cy.get('#error-email').should('not.exist')
cy.get('#error-password').should('not.exist')
})
it('should fails with invalid credentials', () => {
cy.task('startMockServer', [
...authenticationHandlers,
postUsersSigninInvalidCredentialsHandler
])
cy.get('#error-email').should('not.exist')
cy.get('#error-password').should('not.exist')
cy.get('[data-cy=input-email]').type(user.email)
cy.get('[data-cy=input-password]').type('randompassword')
cy.get('[data-cy=submit]').click()
cy.get('#message').should(
'have.text',
'Error: Invalid credentials. Please try again.'
)
cy.get('#error-email').should('not.exist')
cy.get('#error-password').should('not.exist')
})
})

View File

@ -0,0 +1,85 @@
import { user } from '../../../fixtures/users/user'
import {
postUsersSignupHandler,
postUsersSignupAlreadyUsedHandler
} from '../../../fixtures/users/signup/post'
describe('Pages > /authentication/signup', () => {
beforeEach(() => {
cy.task('stopMockServer')
cy.visit('/authentication/signup')
})
it('should succeeds and sign up the user', () => {
cy.task('startMockServer', [postUsersSignupHandler])
cy.get('#error-name').should('not.exist')
cy.get('#error-email').should('not.exist')
cy.get('#error-password').should('not.exist')
cy.get('[data-cy=input-name]').type(user.name)
cy.get('[data-cy=input-email]').type(user.email)
cy.get('[data-cy=input-password]').type('randompassword')
cy.get('[data-cy=submit]').click()
cy.get('#message').should(
'have.text',
"Success: You're almost there, please check your emails to confirm registration."
)
})
it('should fails with name or email already used', () => {
cy.task('startMockServer', [postUsersSignupAlreadyUsedHandler])
cy.get('#error-name').should('not.exist')
cy.get('#error-email').should('not.exist')
cy.get('#error-password').should('not.exist')
cy.get('[data-cy=input-name]').type(user.name)
cy.get('[data-cy=input-email]').type(user.email)
cy.get('[data-cy=input-password]').type('randompassword')
cy.get('[data-cy=submit]').click()
cy.get('#message').should('have.text', 'Error: Name or Email already used.')
cy.get('#error-name').should('not.exist')
cy.get('#error-email').should('not.exist')
cy.get('#error-password').should('not.exist')
})
it('should fails with unreachable api server', () => {
cy.get('#error-name').should('not.exist')
cy.get('#error-email').should('not.exist')
cy.get('#error-password').should('not.exist')
cy.get('[data-cy=input-name]').type(user.name)
cy.get('[data-cy=input-email]').type(user.email)
cy.get('[data-cy=input-password]').type('randompassword')
cy.get('[data-cy=submit]').click()
cy.get('#message').should('have.text', 'Error: Internal Server Error.')
cy.get('#error-name').should('not.exist')
cy.get('#error-email').should('not.exist')
cy.get('#error-password').should('not.exist')
})
it('should fails with all inputs as required with error messages and update error messages when updating language (translation)', () => {
const requiredErrorMessage = {
en: 'Error: Oops, this field is required 🙈.',
fr: 'Erreur: Oups, ce champ est obligatoire 🙈.'
}
cy.get('#error-name').should('not.exist')
cy.get('#error-email').should('not.exist')
cy.get('#error-password').should('not.exist')
cy.get('[data-cy=submit]').click()
cy.get('#error-name').should('have.text', requiredErrorMessage.en)
cy.get('#error-email').should('have.text', requiredErrorMessage.en)
cy.get('#error-password').should('have.text', requiredErrorMessage.en)
cy.get('[data-cy=language-click]').click()
cy.get('[data-cy=languages-list] > li:first-child').contains('FR').click()
cy.get('#error-name').should('have.text', requiredErrorMessage.fr)
cy.get('#error-email').should('have.text', requiredErrorMessage.fr)
cy.get('#error-password').should('have.text', requiredErrorMessage.fr)
})
it('should fails with wrong email format', () => {
cy.get('#error-email').should('not.exist')
cy.get('[data-cy=input-email]').type('test')
cy.get('[data-cy=submit]').click()
cy.get('#error-email').should(
'have.text',
'Error: Mmm… It seems that this email is not valid 🤔.'
)
})
})

View File

@ -0,0 +1,12 @@
describe('Page > /', () => {
beforeEach(() => {
cy.visit('/')
})
it('should redirect the user to signup page when clicking "Get started"', () => {
cy.get('[data-cy=get-started]')
.click()
.location('pathname')
.should('eq', '/authentication/signup')
})
})

40
cypress/plugins/index.js Normal file
View File

@ -0,0 +1,40 @@
import { getLocal } from 'mockttp'
/// <reference types="cypress" />
/** @type {import('mockttp').Mockttp | null} */
let server = null
/**
* @type {Cypress.PluginConfig}
*/
module.exports = (on, config) => {
on('task', {
/**
* @param {import('../fixtures/handler').Handlers} handlers
*/
async startMockServer(handlers) {
server = getLocal({
cors: true
})
await server.start(8080)
for (const handler of handlers) {
await server[handler.method.toLowerCase()](handler.url).thenJson(
handler.response.statusCode,
handler.response.body
)
}
return null
},
async stopMockServer() {
if (server != null) {
await server.stop()
server = null
}
return null
}
})
return config
}