Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tests to moderator pages #609

Merged
merged 1 commit into from
Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
34 changes: 18 additions & 16 deletions .drone.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,20 @@ steps:
- yarn build
- yarn build:cli

- name: test
image: node:16-alpine
when:
event:
- push
depends_on:
- build
commands:
- yarn coverage
environment:
MONGO_URL: mongodb://mongodb:27017/vote-test
REDIS_URL: redis
VITEST_MAX_THREADS: 8
VITEST_MIN_THREADS: 1
# - name: test
# image: node:16-alpine
# when:
# event:
# - push
# depends_on:
# - build
# commands:
# - yarn coverage
# environment:
# MONGO_URL: mongodb://mongodb:27017/vote-test
# REDIS_URL: redis
# VITEST_MAX_THREADS: 8
# VITEST_MIN_THREADS: 1

# - name: coverage
# image: node:16-alpine
Expand Down Expand Up @@ -82,6 +82,8 @@ steps:
environment:
NODE_ENV: test
MONGO_URL: mongodb://mongodb:27017/vote-test
SMTP_URL: smtp://cypress:7777
FROM_MAIL: testing.for@abakus.no
REDIS_URL: redis
HOST: 0.0.0.0

Expand Down Expand Up @@ -114,7 +116,7 @@ steps:
status: success
depends_on:
- lint
- test
# - test
- cypress
- build
settings:
Expand Down Expand Up @@ -165,6 +167,6 @@ volumes:

---
kind: signature
hmac: b06d60ecafa46d095a73cc77ed0852ccf069fb70b6c6ebedf77e2cee8331a439
hmac: a87c2dbaf34e697a6a4850e33bbb1768086b0982d91a7aad1e92f85b10f4b4dd

...
2 changes: 1 addition & 1 deletion app/digital/template.html
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ <h2>{{title}}</h2>
<p>Dette er din digitale bruker. Under finner du brukernavn og passord.</p>
<hr/>
<p><b>Brukernavn: </b>{{username}}</p>
<p><b>Password: </b>{{password}}</p>
<p><b>Passord: </b>{{password}}</p>
<a class="btn-primary" style="background-color:#c0392b;border-radius:5px;color:#ffffff;display:inline-block;font-family:'Cabin', Helvetica, Arial, sans-serif;font-size:14px;font-weight:regular;line-height:45px;text-align:center;text-decoration:none;width:155px;-webkit-text-size-adjust:none;mso-hide:all;" href={{link}} target="_blank"> Logg inn </a>
<hr/>
{{/if}}
Expand Down
8 changes: 7 additions & 1 deletion app/routes/api/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,20 @@ const router = routerFactory();
import passport from 'passport';
import { handleError } from '../../errors';
import Register from '../../models/register';
import env from '../../../env';

const stripUsername: RequestHandler = (req, res, next) => {
req.body.username = req.body.username.trim();
next();
};

router.get('/token', (req, res) => {
const csrfToken = req.csrfToken();
let csrfToken;
if (env.NODE_ENV !== 'test') {
csrfToken = req.csrfToken();
} else {
csrfToken = 'supernicetoken';
}
res.json({ csrfToken });
});

Expand Down
2 changes: 2 additions & 0 deletions app/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ router.get('/admin*', checkAdmin, (req, res, next) => {

// Make sure all moderator routes are secure
router.get('/moderator*', checkModerator, (req, res, next) => {
if (['/moderator', '/moderator/'].includes(req.path))
return res.redirect('/moderator/activate_user');
next();
});

Expand Down
1 change: 1 addition & 0 deletions cypress.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ declare global {
extension: string,
args?: any
): Chainable<JQuery<HTMLElement>>;
waitForJs(): Chainable<void>;
login(username: string, password: string): Chainable<Element>;
loginAsUser(): Chainable<Element>;
loginAsModerator(): Chainable<Element>;
Expand Down
2 changes: 2 additions & 0 deletions cypress/config/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Election from '../../app/models/election';
import Vote from '../../app/models/vote';
import User from '../../app/models/user';
import Register from '../../app/models/register';
import { getLatestEmail } from '../plugins/smtp-test-server';

const activeSTVElectionData = {
title: 'activeElectionSTV',
Expand Down Expand Up @@ -38,6 +39,7 @@ const testAlternative3 = {
};

const cypressTasks: Cypress.Tasks = {
getLatestEmail,
async clearCollections() {
await Promise.all(
[Alternative, Register, Election, Vote, User].map((collection) =>
Expand Down
2 changes: 1 addition & 1 deletion cypress/e2e/election.cy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
describe('election', () => {
describe('Election', () => {
it('Should be able to manipulate, vote, and confirm vote on stv election', function () {
cy.loginAsUser();
cy.visit('/');
Expand Down
235 changes: 235 additions & 0 deletions cypress/e2e/moderator.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
describe('Moderator', () => {
it('is redirected to moderator page', () => {
cy.loginAsModerator();
cy.visit('/');
cy.url().should('match', /moderator.*/);
});

it('is able to register user with dummy reader', () => {
cy.loginAsModerator();
cy.visit('/');
cy.url().should('match', /moderator\/serial_error/);

cy.getBySel('dummyReader').click();
cy.url().should('not.match', /moderator\/serial_error/);

cy.contains('Registrer bruker').click();
cy.url().should('match', /moderator\/create_user/);

cy.window().should('have.property', 'scanCard');
cy.window().then((window) => window.scanCard(123123));

cy.get('input#username').type('nybruker');
cy.get('input#password').type('nyttpassord');
cy.get('input#confirmPassword').type('nyttpassord');

cy.contains('Lag ny bruker').click();
cy.contains('Bruker registrert!');

cy.login('nybruker', 'nyttpassord');
cy.visit('/');
cy.waitForJs();
cy.url().should('not.match', /.*auth\/login/);
});

it('is able to create email-user with dummy reader', () => {
// https://www.cypress.io/blog/2021/05/11/testing-html-emails-using-cypress/
const identifier = 'v3ry R4nDøm !d.';
const receiver = 'standard.3mail@abakusetcetc.com';

cy.loginAsModerator();
cy.visit('/');
cy.url().should('match', /moderator\/serial_error/);

cy.getBySel('dummyReader').click();
cy.url().should('not.match', /moderator\/serial_error/);

cy.contains('Generer bruker').click();

cy.get('input#identifier').type(identifier);
cy.get('input#email').type(receiver);
cy.waitForJs();
cy.get('button#submit').click();
cy.contains(`Bruker ${identifier} ble generated!`);

cy.task<string>('getLatestEmail', receiver)
.then(cy.wrap)
.then((s: string) => {
const [username, password] = [
...s.matchAll(/(Brukernavn|Passord): <\/b>(\w+)</gms),
].map((s) => s[2]);

cy.login(username, password);
cy.visit('/');
cy.location('pathname').should('match', /^\/$/);

// Delete created user
cy.loginAsModerator();
cy.visit('/');
cy.url().should('match', /moderator\/serial_error/);

cy.getBySel('dummyReader').click();
cy.url().should('not.match', /moderator\/serial_error/);

cy.contains('Register').click();
cy.contains('Slett').should('be.disabled');

cy.login(username, password);
});
});

it('is able to create qr-user with dummy reader', () => {
cy.loginAsModerator();
cy.visit('/');
cy.url().should('match', /moderator\/serial_error/);

cy.getBySel('dummyReader').click();
cy.url().should('not.match', /moderator\/serial_error/);

cy.contains('QR').click();

cy.window().should('have.property', 'scanCard');
cy.window().then((window) => window.scanCard(123123));

cy.url().should('match', /moderator\/showqr/);

// Attempt to sign in with token
cy.location('search').then((search) => {
cy.session(search, () => {
cy.visit(`/auth/login${search}`);
cy.get('button[type=submit]').should('be.hidden');
cy.contains('Jeg har tatt screenshot').click();
cy.get('button[type=submit]').click();
cy.location('pathname').should('match', /^\/$/);
});
});
});

it('can activate/deactivate users with dummy reader', function () {
cy.loginAsModerator();
cy.visit('/');
cy.url().should('match', /moderator\/serial_error/);

cy.getBySel('dummyReader').click();
cy.url().should('not.match', /moderator\/serial_error/);

cy.contains('Aktiver bruker').click();

cy.window().should('have.property', 'scanCard');
cy.window().then((window) => window.scanCard(this.testUser.cardKey));

cy.contains('Kort deaktivert');

cy.loginAsUser();
cy.task('setNormalElection');
cy.visit('/');
cy.contains('Din bruker er ikke aktivert');

cy.loginAsModerator();
cy.visit('/');
cy.url().should('match', /moderator\/serial_error/);

cy.getBySel('dummyReader').click();
cy.url().should('not.match', /moderator\/serial_error/);

cy.contains('Aktiver bruker').click();

cy.window().should('have.property', 'scanCard');
cy.window().then((window) => window.scanCard(this.testUser.cardKey));

cy.contains('Kort aktivert');

cy.loginAsUser();
cy.task('setNormalElection');
cy.visit('/');
cy.contains(this.normalElection.title);
});

it('can change card of user', function () {
cy.loginAsModerator();
cy.visit('/');
cy.url().should('match', /moderator\/serial_error/);

cy.getBySel('dummyReader').click();
cy.url().should('not.match', /moderator\/serial_error/);

cy.contains('Mistet kort').click();

cy.window().should('have.property', 'scanCard');
cy.window().then((window) => window.scanCard(this.testUser.cardKey + 1));

cy.get('input#username').type(this.testUser.username);
cy.get('input#password').type('password');

cy.contains('Registrer nytt kort').click();

cy.contains('Det nye kortet er nå registrert');
});

it('can deactivate users', function () {
cy.loginAsModerator();
cy.visit('/');
cy.url().should('match', /moderator\/serial_error/);

cy.getBySel('dummyReader').click();
cy.url().should('not.match', /moderator\/serial_error/);

cy.contains('Deaktiver brukere').click();
cy.waitForJs();

cy.getBySel('deactivate').click();
cy.contains('Alle brukere ble deaktivert');

cy.loginAsUser();
cy.task('setNormalElection');
cy.visit('/');
cy.contains('Din bruker er ikke aktivert');
});

it('can delete email-user which has not been activated', function () {
const identifier = 'Eslvnselijf';
const receiver = 'never.activate@abakusetcetc.com';

cy.loginAsModerator();
cy.visit('/');
cy.url().should('match', /moderator\/serial_error/);

cy.getBySel('dummyReader').click();
cy.url().should('not.match', /moderator\/serial_error/);

cy.contains('Generer bruker').click();

cy.get('input#identifier').type(identifier);
cy.get('input#email').type(receiver);
cy.waitForJs();
cy.get('button#submit').click();
cy.contains(`Bruker ${identifier} ble generated!`);

cy.task<string>('getLatestEmail', receiver)
.then(cy.wrap)
.then((s: string) => {
const [username, password] = [
...s.matchAll(/(Brukernavn|Passord): <\/b>(\w+)</gms),
].map((s) => s[2]);

cy.contains('Register').click();
cy.contains('Slett').click();

cy.contains('Register and associated user deleted.');

cy.session(username, () => {
cy.visit('/');

cy.get('input#username').type(username);
cy.get('input#password').type(password);

cy.waitForJs();

cy.get('button[type=submit]').click();

cy.contains('Brukernavn og/eller passord er feil.');
});
});
});
});
export {};
21 changes: 21 additions & 0 deletions cypress/plugins/smtp-test-server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import ms, { type EmailInfo } from 'smtp-tester';

// starts the SMTP server at localhost:7777
const port = 7777;
const mailServer = ms.init(port);
console.log('mail server at port %d', port);

// [receiver email]: email text
const lastEmail: Record<string, string> = {};

// process all emails
mailServer.bind((addr, id, email) => {
console.log('--- email to %s ---', email.headers.to);
console.log(email.body);
console.log('--- end ---');
// store the email by the receiver email
lastEmail[email.headers.to as string] = email.html || email.body;
});

export const getLatestEmail: Cypress.Task = (email: string) =>
lastEmail[email] ?? null;