Merge branch 'test/add-cypress-on-event-creation' into 'master'
Add e2e seed and test event creation See merge request framasoft/mobilizon!254
This commit is contained in:
commit
2577a2a27b
|
@ -91,6 +91,7 @@ cypress:
|
|||
- cd ../
|
||||
- MIX_ENV=e2e mix ecto.create
|
||||
- MIX_ENV=e2e mix ecto.migrate
|
||||
- MIX_ENV=e2e mix run priv/repo/e2e.seed.exs
|
||||
- MIX_ENV=e2e mix phx.server &
|
||||
- cd js
|
||||
- npx wait-on http://localhost:4000
|
||||
|
|
|
@ -7,7 +7,8 @@ import Config
|
|||
|
||||
# General application configuration
|
||||
config :mobilizon,
|
||||
ecto_repos: [Mobilizon.Storage.Repo]
|
||||
ecto_repos: [Mobilizon.Storage.Repo],
|
||||
env: Mix.env()
|
||||
|
||||
config :mobilizon, :instance,
|
||||
name: System.get_env("MOBILIZON_INSTANCE_NAME") || "My Mobilizon Instance",
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<template>
|
||||
<span v-if="!endsOn">{{ beginsOn | formatDateTimeString }}</span>
|
||||
<span v-else-if="isSameDay()">
|
||||
{{ $t('The {date} from {startTime} to {endTime}', {date: formatDate(beginsOn), startTime: formatTime(beginsOn), endTime: formatTime(endsOn)}) }}
|
||||
{{ $t('On {date} from {startTime} to {endTime}', {date: formatDate(beginsOn), startTime: formatTime(beginsOn), endTime: formatTime(endsOn)}) }}
|
||||
</span>
|
||||
<span v-else-if="endsOn">
|
||||
{{ $t('From the {startDate} at {startTime} to the {endDate} at {endTime}',
|
||||
|
|
|
@ -137,8 +137,8 @@ export default class NavBar extends Vue {
|
|||
}
|
||||
}
|
||||
|
||||
async handleErrors(errors: GraphQLError) {
|
||||
if (errors[0].message === 'You need to be logged-in to view your list of identities') {
|
||||
async handleErrors(errors: GraphQLError[]) {
|
||||
if (errors.length > 0 && errors[0].message === 'You need to be logged-in to view your list of identities') {
|
||||
await this.logout();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -145,6 +145,7 @@
|
|||
"No results for \"{queryText}\"": "No results for \"{queryText}\"",
|
||||
"Number of places": "Number of places",
|
||||
"Old password": "Old password",
|
||||
"On {date} from {startTime} to {endTime}": "On {date} from {startTime} to {endTime}",
|
||||
"One person is going": "No one is going | One person is going | {approved} persons are going",
|
||||
"Only accessible through link and search (private)": "Only accessible through link and search (private)",
|
||||
"Opened reports": "Opened reports",
|
||||
|
@ -218,13 +219,13 @@
|
|||
"The page you're looking for doesn't exist.": "The page you're looking for doesn't exist.",
|
||||
"The password was successfully changed": "The password was successfully changed",
|
||||
"The report will be sent to the moderators of your instance. You can explain why you report this content below.": "The report will be sent to the moderators of your instance. You can explain why you report this content below.",
|
||||
"The {date} from {startTime} to {endTime}": "The {date} from {startTime} to {endTime}",
|
||||
"These events may interest you": "These events may interest you",
|
||||
"This installation (called “instance“) can easily {interconnect}, thanks to {protocol}.": "This installation (called “instance“) can easily {interconnect}, thanks to {protocol}.",
|
||||
"This instance isn't opened to registrations, but you can register on other instances.": "This instance isn't opened to registrations, but you can register on other instances.",
|
||||
"This is a demonstration site to test the beta version of Mobilizon.": "This is a demonstration site to test the beta version of Mobilizon.",
|
||||
"This will delete / anonymize all content (events, comments, messages, participations…) created from this identity.": "This will delete / anonymize all content (events, comments, messages, participations…) created from this identity.",
|
||||
"Title": "Title",
|
||||
"To achieve your registration, please create a first identity profile.": "To achieve your registration, please create a first identity profile.",
|
||||
"To change the world, change the software": "To change the world, change the software",
|
||||
"To confirm, type your identity username \"{preferredUsername}\"": "To confirm, type your identity username \"{preferredUsername}\"",
|
||||
"Transfer to {outsideDomain}": "Transfer to {outsideDomain}",
|
||||
|
|
|
@ -170,6 +170,7 @@
|
|||
"No results for \"{queryText}\"": "Pas de résultats pour « {queryText} »",
|
||||
"Number of places": "Nombre de places",
|
||||
"Old password": "Ancien mot de passe",
|
||||
"On {date} from {startTime} to {endTime}": "On {date} de {startTime} à {endTime}",
|
||||
"One person is going": "Personne n'y va | Une personne y va | {approved} personnes y vont",
|
||||
"Only accessible through link and search (private)": "Uniquement accessibles par lien et la recherche (privé)",
|
||||
"Opened reports": "Signalements ouverts",
|
||||
|
@ -250,7 +251,6 @@
|
|||
"The password was successfully changed": "Le mot de passe a été changé avec succès",
|
||||
"The report will be sent to the moderators of your instance. You can explain why you report this content below.": "Le signalement sera envoyé aux modérateur⋅ices de votre instance. Vous pouvez expliquer pourquoi vous signalez ce contenu ci-dessous.",
|
||||
"The {date} at {time}": "Le {date} à {time}",
|
||||
"The {date} from {startTime} to {endTime}": "Le {date} de {startTime} à {endTime}",
|
||||
"There are {participants} participants.": "Il n'y a qu'un⋅e participant⋅e. | Il y a {participants} participants.",
|
||||
"These events may interest you": "Ces événements peuvent vous intéresser",
|
||||
"This installation (called “instance“) can easily {interconnect}, thanks to {protocol}.": "Cette installation (appelée “instance“) peut facilement {interconnect}, grâce à {protocol}.",
|
||||
|
@ -258,6 +258,7 @@
|
|||
"This is a demonstration site to test the beta version of Mobilizon.": "Ceci est un site de démonstration permettant de tester la version bêta de Mobilizon.",
|
||||
"This will delete / anonymize all content (events, comments, messages, participations…) created from this identity.": "Cela supprimera / anonymisera tout le contenu (événements, commentaires, messages, participations…) créés avec cette identité.",
|
||||
"Title": "Titre",
|
||||
"To achieve your registration, please create a first identity profile.": "Pour finir votre inscription, veuillez créer un premier profil.",
|
||||
"To change the world, change the software": "Changer de logiciel pour changer le monde",
|
||||
"To confirm, type your event title \"{eventTitle}\"": "Pour confirmer, entrez le titre de l'événement « {eventTitle} »",
|
||||
"To confirm, type your identity username \"{preferredUsername}\"": "Pour confirmer, entrez le nom de l’identité « {preferredUsername} »",
|
||||
|
|
|
@ -37,6 +37,8 @@ export function deleteUserData() {
|
|||
}
|
||||
}
|
||||
|
||||
export class NoIdentitiesException extends Error {}
|
||||
|
||||
/**
|
||||
* We fetch from localStorage the latest actor ID used,
|
||||
* then fetch the current identities to set in cache
|
||||
|
@ -50,7 +52,10 @@ export async function initializeCurrentActor(apollo: ApolloClient<any>) {
|
|||
fetchPolicy: 'network-only',
|
||||
});
|
||||
const identities = result.data.identities;
|
||||
if (identities.length < 1) return;
|
||||
if (identities.length < 1) {
|
||||
console.warn('Logged user has no identities!');
|
||||
throw new NoIdentitiesException;
|
||||
}
|
||||
const activeIdentity = identities.find(identity => identity.id === actorId) || identities[0] as IPerson;
|
||||
|
||||
if (activeIdentity) {
|
||||
|
|
|
@ -5,7 +5,10 @@
|
|||
<h1 class="title">
|
||||
{{ $t('Register an account on Mobilizon!') }}
|
||||
</h1>
|
||||
<form v-if="!validationSent">
|
||||
<b-message v-if="userAlreadyActivated">
|
||||
{{ $t('To achieve your registration, please create a first identity profile.')}}
|
||||
</b-message>
|
||||
<form v-if="!validationSent" @submit.prevent="submit">
|
||||
<b-field
|
||||
:label="$t('Username')"
|
||||
:type="errors.preferred_username ? 'is-danger' : null"
|
||||
|
@ -33,7 +36,7 @@
|
|||
</b-field>
|
||||
|
||||
<p class="control has-text-centered">
|
||||
<b-button type="is-primary" size="is-large" @click="submit()">
|
||||
<b-button type="is-primary" size="is-large" native-type="submit">
|
||||
{{ $t('Create my profile') }}
|
||||
</b-button>
|
||||
</p>
|
||||
|
@ -117,8 +120,8 @@ export default class Register extends Vue {
|
|||
acc[error.details] = error.message;
|
||||
return acc;
|
||||
}, {});
|
||||
console.error(error);
|
||||
console.error(this.errors);
|
||||
console.error('Error while registering person', error);
|
||||
console.error('Errors while registering person', this.errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import { LOGIN } from '@/graphql/auth';
|
||||
import { validateEmailField, validateRequiredField } from '@/utils/validators';
|
||||
import { initializeCurrentActor, saveUserData } from '@/utils/auth';
|
||||
import { initializeCurrentActor, NoIdentitiesException, saveUserData } from '@/utils/auth';
|
||||
import { ILogin } from '@/types/login.model';
|
||||
import { CURRENT_USER_CLIENT, UPDATE_CURRENT_USER_CLIENT } from '@/graphql/user';
|
||||
import { onLogin } from '@/vue-apollo';
|
||||
|
@ -153,7 +153,16 @@ export default class Login extends Vue {
|
|||
role: data.login.user.role,
|
||||
},
|
||||
});
|
||||
await initializeCurrentActor(this.$apollo.provider.defaultClient);
|
||||
try {
|
||||
await initializeCurrentActor(this.$apollo.provider.defaultClient);
|
||||
} catch (e) {
|
||||
if (e instanceof NoIdentitiesException) {
|
||||
return await this.$router.push({
|
||||
name: RouteName.REGISTER_PROFILE,
|
||||
params: { email: this.currentUser.email, userAlreadyActivated: 'true' },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onLogin(this.$apollo);
|
||||
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
// https://docs.cypress.io/api/introduction/api.html
|
||||
import { onBeforeLoad } from './browser-language';
|
||||
|
||||
beforeEach(() => {
|
||||
cy.restoreLocalStorage();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cy.saveLocalStorage();
|
||||
});
|
||||
|
||||
describe('Homepage', () => {
|
||||
it('Checks the footer', () => {
|
||||
cy.visit('/', { onBeforeLoad });
|
||||
|
|
44
js/tests/e2e/specs/event.js
Normal file
44
js/tests/e2e/specs/event.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
import { onBeforeLoad } from './browser-language';
|
||||
|
||||
beforeEach(() => {
|
||||
cy.clearLocalStorage();
|
||||
cy.checkoutSession();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cy.dropSession();
|
||||
});
|
||||
|
||||
describe('Events', () => {
|
||||
it('Shows my current events', () => {
|
||||
const EVENT = { title: 'My first event'};
|
||||
|
||||
cy.loginUser();
|
||||
cy.visit('/events/me', { onBeforeLoad });
|
||||
cy.contains('.message.is-danger', 'No events found');
|
||||
cy.contains('.navbar-item', 'Create').click();
|
||||
|
||||
cy.url().should('include', 'create');
|
||||
cy.get('.field').first().find('input').type(EVENT.title);
|
||||
cy.get('.field').eq(1).find('input').type('my tag, holo{enter}');
|
||||
cy.get('.field').eq(2).find('.datepicker .dropdown-trigger').click();
|
||||
|
||||
cy.get('.field').eq(3).find('.pagination-list .control').first().find('.select select').select('September');
|
||||
cy.get('.field').eq(3).find('.pagination-list .control').last().find('.select select').select('2021');
|
||||
cy.wait(1000);
|
||||
cy.get('.field').eq(3).contains('.datepicker-cell', '15').click();
|
||||
|
||||
cy.contains('.button.is-primary', 'Create my event').click();
|
||||
cy.url().should('include', '/events/');
|
||||
cy.contains('.title', EVENT.title);
|
||||
cy.contains('.title-and-informations span small', 'You\'re the only one going to this event');
|
||||
cy.contains('.date-and-privacy', 'On Wednesday, September 15, 2021 from');
|
||||
cy.contains('.visibility .tag', 'Public event');
|
||||
|
||||
cy.contains('.navbar-item', 'My events').click();
|
||||
cy.contains('.title', EVENT.title);
|
||||
cy.contains('.content.column', 'You\'re organizing this event');
|
||||
cy.contains('.title-wrapper .date-component .datetime-container .month', 'Sep');
|
||||
cy.contains('.title-wrapper .date-component .datetime-container .day', '15');
|
||||
});
|
||||
});
|
|
@ -1,11 +1,7 @@
|
|||
import { onBeforeLoad } from './browser-language';
|
||||
|
||||
beforeEach(() => {
|
||||
cy.restoreLocalStorage();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cy.saveLocalStorage();
|
||||
cy.clearLocalStorage();
|
||||
});
|
||||
|
||||
describe('Login', () => {
|
||||
|
@ -42,4 +38,46 @@ describe('Login', () => {
|
|||
|
||||
cy.contains('.message.is-danger', 'User with email not found');
|
||||
});
|
||||
|
||||
it('Tries to login with valid credentials', () => {
|
||||
cy.visit('/login', { onBeforeLoad });
|
||||
cy.get('input[type=email]').type('user@email.com');
|
||||
cy.get('input[type=password]').type('some password');
|
||||
cy.get('form').submit();
|
||||
cy.contains('.navbar-link', 'test_user');
|
||||
cy.contains('article.message.is-info', 'Welcome back I\'m a test user');
|
||||
cy.contains('.navbar-item.has-dropdown', 'test_user').click();
|
||||
cy.get('.navbar-item').last().contains('Log out').click();
|
||||
});
|
||||
|
||||
it('Tries to login with valid credentials but unconfirmed account', () => {
|
||||
cy.visit('/login', { onBeforeLoad });
|
||||
cy.get('input[type=email]').type('unconfirmed@email.com');
|
||||
cy.get('input[type=password]').type('some password');
|
||||
cy.get('form').submit();
|
||||
cy.contains('.message.is-danger', 'User with email not found');
|
||||
});
|
||||
|
||||
it('Tries to login with valid credentials, confirmed account but no profile', () => {
|
||||
cy.visit('/login', { onBeforeLoad });
|
||||
cy.get('input[type=email]').type('confirmed@email.com');
|
||||
cy.get('input[type=password]').type('some password');
|
||||
cy.get('form').submit();
|
||||
|
||||
cy.contains('.message', 'To achieve your registration, please create a first identity profile.');
|
||||
cy.get('form .field').first().contains('label', 'Username').parent().find('input').type('test_user');
|
||||
cy.get('form .field').eq(2).contains('label', 'Displayed name').parent().find('input').type('Duplicate');
|
||||
cy.get('form .field').eq(3).contains('label', 'Description').parent().find('textarea').type('This shouln\'t work because it\' using a dupublicated username');
|
||||
cy.get('.control.has-text-centered').contains('button', 'Create my profile').click();
|
||||
cy.contains('.help.is-danger', 'Username is already taken');
|
||||
|
||||
cy.get('form .field input').first(0).clear().type('test_user_2');
|
||||
cy.get('form .field input').eq(1).type('Not');
|
||||
cy.get('form .field textarea').clear().type('This will now work');
|
||||
cy.get('form').submit();
|
||||
cy.wait(1000);
|
||||
|
||||
cy.contains('.navbar-link', 'test_user_2');
|
||||
cy.contains('article.message.is-info', 'Welcome back DuplicateNot');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import { onBeforeLoad } from './browser-language';
|
||||
|
||||
beforeEach(() => {
|
||||
cy.restoreLocalStorage();
|
||||
cy.checkoutSession();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cy.saveLocalStorage();
|
||||
cy.dropSession();
|
||||
});
|
||||
|
||||
|
@ -34,7 +32,7 @@ describe('Registration', () => {
|
|||
|
||||
it('Tests that registration works', () => {
|
||||
cy.visit('/register/user', { onBeforeLoad });
|
||||
cy.get('input[type=email]').type('user@email.com');
|
||||
cy.get('input[type=email]').type('user2register@email.com');
|
||||
cy.get('input[type=password]').type('userPassword');
|
||||
cy.get('form').contains('button.button.is-primary', 'Register').click();
|
||||
|
||||
|
@ -45,7 +43,7 @@ describe('Registration', () => {
|
|||
cy.get('form .field').eq(3).contains('label', 'Description').parent().find('textarea').type('This is a test account');
|
||||
cy.get('.control.has-text-centered').contains('button', 'Create my profile').click();
|
||||
|
||||
cy.contains('article.message.is-success', 'Your account is nearly ready, tester').contains('A validation email was sent to user@email.com');
|
||||
cy.contains('article.message.is-success', 'Your account is nearly ready, tester').contains('A validation email was sent to user2register@email.com');
|
||||
|
||||
cy.visit('/sent_emails');
|
||||
|
||||
|
|
|
@ -24,6 +24,14 @@
|
|||
// -- This is will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
||||
|
||||
const AUTH_ACCESS_TOKEN = 'auth-access-token';
|
||||
const AUTH_REFRESH_TOKEN = 'auth-refresh-token';
|
||||
const AUTH_USER_ID = 'auth-user-id';
|
||||
const AUTH_USER_EMAIL = 'auth-user-email';
|
||||
const AUTH_USER_ACTOR_ID = 'auth-user-actor-id';
|
||||
const AUTH_USER_ROLE = 'auth-user-role';
|
||||
|
||||
|
||||
let LOCAL_STORAGE_MEMORY = {};
|
||||
|
||||
Cypress.Commands.add("saveLocalStorage", () => {
|
||||
|
@ -38,6 +46,54 @@ Cypress.Commands.add("restoreLocalStorage", () => {
|
|||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add("clearLocalStorage", () => {
|
||||
Object.keys(LOCAL_STORAGE_MEMORY).forEach(key => {
|
||||
localStorage.removeItem(key);
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add("loginUser", () => {
|
||||
const loginMutation = `
|
||||
mutation Login($email: String!, $password: String!) {
|
||||
login(email: $email, password: $password) {
|
||||
accessToken,
|
||||
refreshToken,
|
||||
user {
|
||||
id,
|
||||
email,
|
||||
role
|
||||
}
|
||||
},
|
||||
}`;
|
||||
|
||||
const body = JSON.stringify({
|
||||
operationName: 'Login',
|
||||
query: loginMutation,
|
||||
variables: { email: 'user@email.com', password: 'some password' }
|
||||
});
|
||||
|
||||
cy.request({
|
||||
url: 'http://localhost:4000/api',
|
||||
body: body,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}).then((res) => {
|
||||
console.log(res);
|
||||
const obj = res.body.data.login;
|
||||
console.log(obj);
|
||||
|
||||
localStorage.setItem(AUTH_USER_ID, `${obj.user.id}`);
|
||||
localStorage.setItem(AUTH_USER_EMAIL, obj.user.email);
|
||||
localStorage.setItem(AUTH_USER_ROLE, obj.user.role);
|
||||
|
||||
localStorage.setItem(AUTH_USER_ACTOR_ID, `${obj.id}`);
|
||||
localStorage.setItem(AUTH_ACCESS_TOKEN, obj.accessToken);
|
||||
localStorage.setItem(AUTH_REFRESH_TOKEN, obj.refreshToken);
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('checkoutSession', async () => {
|
||||
const response = await fetch('/sandbox', {
|
||||
cache: 'no-store',
|
||||
|
|
|
@ -77,6 +77,7 @@ defmodule MobilizonWeb.Router do
|
|||
# Because the "/events/:uuid" route caches all these, we need to force them
|
||||
get("/events/create", PageController, :index)
|
||||
get("/events/list", PageController, :index)
|
||||
get("/events/me", PageController, :index)
|
||||
get("/events/:uuid/edit", PageController, :index)
|
||||
|
||||
# This is a hack to ease link generation into emails
|
||||
|
|
57
priv/repo/e2e.seed.exs
Normal file
57
priv/repo/e2e.seed.exs
Normal file
|
@ -0,0 +1,57 @@
|
|||
defmodule EndToEndSeed do
|
||||
alias Mobilizon.Users
|
||||
|
||||
def delete_user(email) do
|
||||
with {:ok, user} <- Users.get_user_by_email(email) do
|
||||
Users.delete_user(user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
alias Mobilizon.Users
|
||||
alias Mobilizon.Users.User
|
||||
alias Mobilizon.Actors
|
||||
alias Mobilizon.Actors.Actor
|
||||
|
||||
if Application.get_env(:mobilizon, :env) != :e2e do
|
||||
exit(:shutdown)
|
||||
end
|
||||
|
||||
# An user that has just been registered
|
||||
test_user_unconfirmed = %{email: "unconfirmed@email.com", password: "some password"}
|
||||
|
||||
# An user that has registered and has confirmed their account, but no attached identity
|
||||
test_user_confirmed = %{email: "confirmed@email.com", password: "some password"}
|
||||
|
||||
# An user that has registered and has confirmed their account, with a profile
|
||||
test_user = %{email: "user@email.com", password: "some password"}
|
||||
test_actor = %{preferred_username: "test_user", name: "I'm a test user", domain: nil}
|
||||
|
||||
EndToEndSeed.delete_user(test_user_unconfirmed.email)
|
||||
EndToEndSeed.delete_user(test_user_confirmed.email)
|
||||
EndToEndSeed.delete_user(test_user.email)
|
||||
|
||||
{:ok, %User{} = _user_unconfirmed} = Users.register(test_user_unconfirmed)
|
||||
|
||||
{:ok, %User{} = user_confirmed} = Users.register(test_user_confirmed)
|
||||
|
||||
Users.update_user(user_confirmed, %{
|
||||
"confirmed_at" => Timex.shift(user_confirmed.confirmation_sent_at, hours: -3),
|
||||
"confirmation_sent_at" => nil,
|
||||
"confirmation_token" => nil
|
||||
})
|
||||
|
||||
{:ok, %User{} = user} = Users.register(test_user)
|
||||
|
||||
Users.update_user(user, %{
|
||||
"confirmed_at" => Timex.shift(user.confirmation_sent_at, hours: -3),
|
||||
"confirmation_sent_at" => nil,
|
||||
"confirmation_token" => nil
|
||||
})
|
||||
|
||||
{:ok, %Actor{}} =
|
||||
Actors.new_person(%{
|
||||
user_id: user.id,
|
||||
preferred_username: test_actor.preferred_username,
|
||||
name: test_actor.name
|
||||
})
|
Loading…
Reference in a new issue