Commit be5360f3 authored by Curtis Adam's avatar Curtis Adam

Merge branch '205-make-automatic-unit-and-acceptancetests-for-all-use-cases' into 'staging'

Resolve "Make automatic Unit- and Acceptancetests for all critical User-Stories"

Closes #205 and #254

See merge request !199
parents 48976b89 6f7b4662
variables:
MIRROR_REPO: git@github.com:thm-projects/arsnova-flashcards.git
NODE_ENV: production
BUILD_DIR: build
BUNDLE_DIR: "$BUILD_DIR/bundle"
chimp:
stage: test
only:
- master
- staging
- /^v[0-9]+/
- /^205-/
tags:
- nodejs
services:
- mongo
dependencies:
- build
variables:
PORT: "3000"
ROOT_URL: "http://localhost:$PORT"
MONGO_HOST: mongo
MONGO_PORT: "27017"
MONGO_DB: meteor-test
MONGO_URL: "mongodb://$MONGO_HOST:$MONGO_PORT/$MONGO_DB"
script:
- export METEOR_SETTINGS="$(cat settings_test.json)"
- mongo --quiet --eval "version();" --host "$MONGO_HOST"
- cd "$BUNDLE_DIR/programs/server" && npm install
- cd -
- node "$BUNDLE_DIR/main.js" &
- sleep 15
- ./tests/runTests.sh
jshint:
stage: test
......@@ -9,7 +40,7 @@ jshint:
dependencies: []
script:
- npm install jshint@^2.9.0
- node_modules/jshint/bin/jshint --config .jshintrc ./client/ ./server/ ./imports/ ./i18n
- node_modules/jshint/bin/jshint --config .jshintrc ./client/ ./server/ ./imports/ ./i18n ./tests
jscs:
stage: test
......@@ -18,7 +49,7 @@ jscs:
dependencies: []
script:
- npm install jscs
- node_modules/jscs/bin/jscs --config .jscsrc ./{client,imports,i18n,server}/
- node_modules/jscs/bin/jscs --config .jscsrc ./{client,imports,i18n,server,tests}/
sonar:
stage: test
......@@ -44,7 +75,7 @@ jsdoc:
- jsdoc -c documentation/conf.json -t ./node_modules/ink-docstrap/template -d build/jsdoc -r
artifacts:
paths:
- build/jsdoc/
- "$BUILD_DIR/jsdoc/"
build:
stage: build
......@@ -52,6 +83,7 @@ build:
- master
- staging
- /^v[0-9]+/
- /^205-/
tags:
- meteor
image: local-nodejs-meteor:1.4
......@@ -59,7 +91,7 @@ build:
script:
- test -d package-metadata && rm -rf /opt/meteor/package-metadata && mv package-metadata /opt/meteor/
- npm install
- meteor build --allow-superuser --server-only --directory build
- meteor build --allow-superuser --server-only --directory "$BUILD_DIR"
- mv /opt/meteor/package-metadata "$CI_PROJECT_DIR/"
cache:
paths:
......@@ -67,7 +99,7 @@ build:
- .meteor/local
artifacts:
paths:
- build/bundle/
- "$BUNDLE_DIR"
deploy_staging:
stage: deploy
......@@ -100,7 +132,7 @@ deploy_production:
- eval $(ssh-agent -s)
- mkdir ~/.ssh && echo "$NEW_PRODUCTION_SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
- ssh-add <(echo "$NEW_PRODUCTION_SSH_PRIVATE_KEY")
- scp -r "build/bundle/"* "$NEW_PRODUCTION_SSH_URL"
- scp -r "$BUNDLE_DIR/"* "$NEW_PRODUCTION_SSH_URL"
environment: production
sync_mirror:
......
......@@ -22,7 +22,7 @@ var jscs = require('gulp-jscs');
var stylish = require('jshint-stylish');
var paths = [
'./{client,imports,i18n,server}/**/*.js'
'./{client,imports,i18n,server,tests}/**/*.js'
];
gulp.task('default', ['codeCheck']);
......
......@@ -50,6 +50,14 @@
"SSR": true,
"process": true,
"Assets": true,
"saveAs": false
"saveAs": false,
"browser": true,
"client": true,
"module": true,
"expect": true,
"Ground": false,
"SSR": true,
"process": true,
"Assets": true
}
}
......@@ -66,3 +66,4 @@ mrt:mathjax
facebook-config-ui
google-config-ui
twitter-config-ui
mizzao:accounts-testing
......@@ -99,6 +99,7 @@ meteorspark:util@0.2.0
minifier-css@1.2.16
minifier-js@1.2.18
minimongo@1.0.21
mizzao:accounts-testing@0.1.0
mizzao:timesync@0.4.0
mizzao:user-status@0.6.6
mobile-experience@1.0.4
......
......@@ -37,6 +37,11 @@ npm install
Then, you can either run the script one time with ```gulp --gulpfile .gulpfile.js``` or you can watch the directory with ```gulp watch --gulpfile .gulpfile.js```
### Automated User Acceptance Tests
Make sure that the user acceptance tests are working.
For more information about user acceptance tests have a look at tests/HOWTOTEST.md [klick](tests/HOWTOTEST.md).
### Project structure
Since Meteor is supporting ES6 (ES2015) we use the import/export statements to modularize the application.
......
......@@ -30,6 +30,17 @@ ServiceConfiguration.configurations.insert({
Meteor.users.after.insert(function (userId, doc) {
// Setup roles for backdoor login, required for acceptance tests
if (Meteor.settings.public.displayLoginButtons.displayTestingBackdoor)
{
Roles.addUsersToRoles(doc._id, [
'standard',
'university',
'admin',
'firstLogin'
]);
}
if (doc.services.cas) {
if (doc.services.cas.id === Meteor.settings.admin.name) {
Roles.addUsersToRoles(doc._id, [
......
......@@ -20,8 +20,8 @@
<tbody>
{{#each allColleges}}
<tr>
<td id="tblCollege">{{college}}</td>
<td id="tblCourse">{{course}}</td>
<td class="tblCollege-{{@index}}">{{college}}</td>
<td class="tblCourse-{{@index}}">{{course}}</td>
<td>
<button class="btn btn-danger btn-raised" type="submit"
id="deleteCollageCourse">{{_ "admin.delete"}}</button>
......@@ -54,9 +54,9 @@
</div>
<div class="row">
<div class="col-md-12">
<button type="submit" id="cancelButton" class="btn btn-default btn-raised pull-left">{{_
<button type="button" id="cancelButton" class="btn btn-default btn-raised pull-left">{{_
"admin.confirm-form.cancel"}}</button>
<button type="submit" id="insertButton" class="btn btn-success btn-raised pull-right">&ensp;{{_
<button type="button" id="insertButton" class="btn btn-success btn-raised pull-right">&ensp;{{_
"admin.confirm-form.settings.save"}}</button>
</div>
</div>
......
......@@ -141,7 +141,7 @@
class="btn btn-danger btn-raised col-xs-12 col-sm-3" onclick="history.go(-1)">{{_
"confirmcard"}}</button>
{{/if}}
<button id=" cardCancel" class="btn btn-default btn-raised cancel col-xs-12 col-sm-3"
<button id="cardCancel" class="btn btn-default btn-raised cancel col-xs-12 col-sm-3"
onclick="history.go(-1)">{{_ "declinecard"}}</button>
<button id="cardSave"
class="btn btn-success btn-raised save pull-right col-xs-12 col-sm-3" {{disableIfOffline}}>
......
......@@ -76,7 +76,7 @@
<div class="panel-body">
<div class="col-sm-8">
<div class="">
<h4>{{{getKind}}} {{name}}</h4>
<h4 id="cardsetTitle">{{{getKind}}} {{name}}</h4>
<hr> {{#markdown}}{{description}}{{/markdown}}
</div>
</div>
......
......@@ -273,7 +273,7 @@
<br/>
<div class="form-group">
<div class="checkbox">
<label>
<label id="select_checkbox">
<input type="checkbox" value="" id="accept_checkbox">
<span class="checkbox-material">
<span class="check"></span>
......
......@@ -57,7 +57,7 @@
</button>
</div>
<div class="btn-group">
<button id="memoRate3" class="btn btn-default btn-raised rate-answe" data-id="3" data-toggle="tooltip"
<button id="memoRate3" class="btn btn-default btn-raised rate-answer" data-id="3" data-toggle="tooltip"
title="{{_ "cardmemo.rate3"}}">3
</button>
</div>
......
......@@ -42,7 +42,7 @@
</a>
</li>
<li class="dropdown col-lg-2">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
<a href="#" class="dropdown-toggle authorBtn" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false">
{{#if hasAuthorFilter}}
{{poolFilterAuthor}}
......@@ -66,7 +66,7 @@
</ul>
</li>
<li class="dropdown col-lg-2">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
<a href="#" class="dropdown-toggle collegeBtn" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false">
{{#if hasCollegeFilter}}
{{poolFilterCollege}}
......@@ -88,7 +88,7 @@
</ul>
</li>
<li class="dropdown col-lg-2">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
<a href="#" class="dropdown-toggle courseBtn" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false" id="collegeBtn">
{{#if hasCourseFilter}}
{{poolFilterCourse}}
......@@ -109,7 +109,7 @@
</ul>
</li>
<li class="dropdown col-lg-1">
<a href="#" id="moduleFilter" class="dropdown-toggle" data-toggle="dropdown" role="button"
<a href="#" id="moduleFilter" class="dropdown-toggle moduleBtn" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false">
{{#if hasModuleFilter}}
{{poolFilterModule}}
......
......@@ -39,6 +39,13 @@ Template.welcome.events({
});
},
// Backdoor for login in acceptance tests
'click #BackdoorLogin': function () {
if (Meteor.settings.public.displayLoginButtons.displayTestingBackdoor) {
Meteor.insecureUserLogin($("#TestingBackdoorUsername").val());
}
},
'click #logout': function () {
Meteor.logout(function (err) {
if (err) {
......@@ -61,4 +68,10 @@ Template.welcome.onRendered(function () {
if (Meteor.settings.public.displayLoginButtons.displayCas) {
$('.panel-footer').append('<a id="cas" href=""><img src="img/social_cas_box_white.png" alt="use CAS for login"/></a>');
}
// Backdoor for login in acceptance tests
if (Meteor.settings.public.displayLoginButtons.displayTestingBackdoor) {
$('.panel-footer').append('<a id="BackdoorLogin" href=""><img src="img/social_backdoor_box_white.png" /></a>');
$('.panel-footer').append('<input id="TestingBackdoorUsername" name="username" placeholder="username">');
}
});
{
"public": {
"rooturl": "http://localhost:3000",
"cas": {
"loginUrl": "https://cas.thm.de/cas/login",
"serviceParam": "service",
"popupWidth": 810,
"popupHeight": 610
},
"displayLoginButtons": {
"displayFacebook": true,
"displayTwitter": true,
"displayGoogle": true,
"displayCas": true,
"displayTestingBackdoor": true
},
"env": "Sandbox",
"BT_MERCHANT_ID": "7jspbjz7zk4vkh67",
"BT_PUBLIC_KEY": "bf7p9crbdv2359gv"
},
"private": {
"BT_PRIVATE_KEY": "b508f71a0e0b01940b0fbe75940debb9"
},
"admin": {
"name": "hg13345"
},
"facebook": {
"api": "162760697401847",
"secret": "151daa9c4bc95310e9e1fb90accd70f8"
},
"twitter": {
"api": "I8LcQC0NetusL9c5F3EFJUXMl",
"secret": "2z0kNAtYcSU5lQKk6FDRR60gxuFawB4TM7tWvnU7yKk6EHHpYh"
},
"google": {
"api": "131328643116-j00jrgjupbbfeft1a9o0p5ling05vsus.apps.googleusercontent.com",
"secret": "2-P5Vy6VI0eOrKIlOCZZq8cR"
},
"cas": {
"baseUrl": "https://cas.thm.de/cas",
"autoClose": true
},
"mail": {
"senderAddress": "info@arsnova.cards",
"language": "de",
"url": "smtp://postmaster%40sandboxcc4938befa8447ca8c857cdf4b968653.mailgun.org:d07ea9c40ccbb21d268bbda17e9aad9d@smtp.mailgun.org:587"
}
}
## How to test the application with chimp and cucumber
## Install NodeJS 6.XX
[https://nodejs.org/en/](https://nodejs.org/en/)
## Install MongoDB
[https://www.mongodb.com](https://www.mongodb.com)
## Install Chrome or Chromium
## Install chimp
npm install -g chimp
### Please follow the folling procedure
#### Enter the project directory
cd flashcards
#### Run Meteor
meteor --settings settings_test.json
## Run all chimp tests
In project root start meteor `meteor --settings settings_test.json`.
Then in another shell enter `./tests/runTests.sh`
## Run a single test
#### Clear the current database
Enter mongo shell with
meteor mongo
Then
use meteor
db.dropDatabase()
Exit mongo shell
#### Load the test data
mongorestore -h 127.0.0.1 --port 3001 -d meteor tests/dump/meteor
#### Run the tests
chimp --ddp=http://localhost:3000 --path=tests/features/yourDirectory
you can add --browser=firefox to run the test on the firefox browser, default browser is chrome
the ci test run uses firefox, due to chrome and phantomjs are not working in the server,
chrome stucks and phantom has problems with script injection from chimp
## Important
For the tests, please login with the user "testuser"
## Helpers
Include the **helper_functions.js** for common functions.
import {login, logout} from "../helper_functions"
now you can use the helper functions:
login("testuser");
...
logout();
## Useful links
[General info about chimp](https://chimp.readme.io/docs/introduction)
[Chimp cheat sheet](https://chimp.readme.io/docs/cheat-sheet)
[Chimp github page](https://github.com/xolvio/chimp)
[Webdriver the API](http://webdriver.io/api.html)
Feature: Delete a cardset in the back end
As an admin,
so that the cardset is not in the system anymore,
the users wants to delete it in the backend.
Background:
Given user is on the site
And user is logged in
And user is in the back end
@watch
Scenario: Admin deletes cardset
When user goes to the menu item cardsets
And user clicks on the delete button
Then the cardset should not be in the list anymore
import {login, logoutAdmin} from "../helper_functions";
module.exports = function () {
'use strict';
var numberOfCardsets = 2;
this.Given(/^user is on the site$/, function () {
browser.url('http://localhost:3000');
});
this.Given(/^user is logged in$/, function () {
login("testuser");
});
this.Given(/^user is in the back end$/, function () {
browser.waitForVisible("#adminpanel", 5000);
browser.click('#adminpanel');
});
this.When(/^user goes to the menu item cardsets$/, function () {
browser.waitForVisible("a[href='/admin/cardsets']",5000);
browser.click("a[href='/admin/cardsets']");
});
this.When(/^user clicks on the delete button$/, function () {
browser.waitForExist('.delete', 5000);
browser.waitForVisible(".deleteCardsetAdmin");
browser.click(".deleteCardsetAdmin");
browser.waitForVisible("#cardsetConfirmModalAdmin", 5000);
browser.click("#cardetDeleteAdmin");
});
this.Then(/^the cardset should not be in the list anymore$/, function () {
browser.waitForVisible("#cardsetConfirmModalAdmin", 5000, true);
browser.waitForVisible('.delete');
var elements = browser.elements(".delete");
expect(elements.value.length - 1).toEqual(numberOfCardsets - 1);
logoutAdmin();
});
};
Feature: Create college and course
As an admin,
so that a cardset can assigned to a college and course,
The user wants to create a college and course.
Background:
Given user is on the site
And user is logged in
And user is in the back end
@watch
Scenario: Admin creates college and course
When user goes to the menu item college
And user creates a new college and course
Then user should see the college and course in list
import {login, logoutAdmin} from "../helper_functions";
module.exports = function () {
'use strict';
this.Given(/^user is on the site$/, function () {
browser.url('http://localhost:3000');
});
this.Given(/^user is logged in$/, function () {
login("testuser");
});
this.Given(/^user is in the back end$/, function () {
browser.waitForVisible("#adminpanel", 5000);
browser.click("#adminpanel");
});
this.When(/^user goes to the menu item college$/, function () {
browser.waitForVisible("a[href='/admin/university']",5000);
browser.click("a[href='/admin/university']");
});
this.When(/^user creates a new college and course$/, function () {
browser.waitForExist('#college', 5000);
browser.setValue('#college', 'THM');
browser.setValue('#courseOfStudies', 'WBS');
browser.waitForVisible('#insertButton');
browser.click("#insertButton");
});
this.Then(/^user should see the college and course in list$/, function () {
browser.waitForExist('.tblCollege-2', 5000);
var college = browser.getText(".tblCollege-2");
var course = browser.getText(".tblCourse-2");
expect(college).toEqual("THM");
expect(course).toEqual("WBS");
logoutAdmin();
});
};
Feature: Change view in cardset/carslist
As a visitor, I whould change the view in cardset to cardlist and reverse.
Background:
Given I am on the site
@watch
Scenario: Visitor can view the cardset
And He loges in
And change to cardset
Then they are on the cardset
And they change the view to cardlist
Then they see the cardlist
And they change the view back to cardset
Then they se cardset again
And they log out
# tests/features/login.feature
import {login, logout} from "../helper_functions";
module.exports = function () {
'use strict';
this.Given(/^I am on the site$/, function () {
// Write code here that turns the phrase above into concrete actions
browser.url('http://localhost:3000');
});
this.Given(/^He loges in$/, function () {
// Write code here that turns the phrase above into concrete actions
login("testuser");
});
this.Given(/^change to cardset$/, function () {
browser.waitForVisible('#cardsets',5000);
browser.click('#cardsets');
browser.waitForVisible("a[href='/cardset/2P6mg5iqCZ49QPPDz']",5000);
browser.click("a[href='/cardset/2P6mg5iqCZ49QPPDz']");
browser.waitForExist('.cardsetInfo', 5000);
});
this.Then(/^they are on the cardset$/, function () {
browser.waitForExist('.cardsetInfo', 5000);
});
this.Then(/^they change the view to cardlist$/, function () {
browser.waitForVisible('#btnToListLayout',5000);
browser.click('#btnToListLayout');
});
this.Then(/^they see the cardlist$/, function () {
browser.waitForExist('#cardset-list', 5000);
});
this.Then(/^they change the view back to cardset$/, function () {
browser.waitForVisible('#btnToCardLayout',5000);
browser.click('#btnToCardLayout');
});
this.Then(/^they se cardset again$/, function () {
browser.waitForExist('.cardsetInfo', 5000);
});
this.Then(/^they log out$/, function () {
logout();
});
};
// tests/features/change_view_steps.js
Feature: Create new deck of cards
As a user of the site,
so that I can create and learn cards,
I want to create a new deck of cards
Background:
Given User is on the site
And User is logged in
And User is on the my cardset view
@watch
Scenario: User creates a new deck of cards
When User clicks on the create cardset button
Then he is redirected to the new cardset form
Then he should be able to edit the cardset title
And he should be able to edit the cardset description
And he should be able to edit the module name
And he should be able to edit the module initials
And he should be able to edit the module ID
And he should be able to choose a college
And he should be able to choose a course
And he should push the create new cardset button
And he should see the created cardset in the my cardset view with the correct values
And he should select the created cardset
And he should see the details of that cardset with the correct values
import {login, logout} from "../helper_functions.js";
module.exports = function () {
'use strict';
var cardsetsBeforeCreated = 0;
var title = "title";
var description = "description";
var module = "module";
var moduleInitials = "short";
var moduleID = "42";
var college = "THM";
var course = "MSP";
this.Given(/^User is on the site$/, function () {
browser.url('http://localhost:3000');
});
this.Given(/^User is logged in$/, function () {
login("testuser");
});
this.Given(/^User is on the my cardset view$/, function () {
browser.waitForVisible('#cardsets',5000);
browser.click('#cardsets');
browser.waitForExist('.cardsetRow', 10000);
cardsetsBeforeCreated = browser.elements('.cardsetRow').value.length;
});
this.When(/^User clicks on the create cardset button$/, function () {
browser.waitForVisible('#newCardSet',5000);
browser.click('#newCardSet');
});
this.Then(/^he is redirected to the new cardset form$/, function () {
var bool = browser.waitForVisible('#newSetModalTitle', 10000);
expect(bool).toBe(true);
});
this.Then(/^he should be able to edit the cardset title$/, function () {
browser.waitForExist('#newSetName',5000);
browser.setValue('#newSetName', title);
});
this.Then(/^he should be able to edit the cardset description$/, function () {
browser.waitForExist('#newSetDescription', 5000);
browser.setValue('#newSetDescription', description);
});
this.Then(/^he should be able to edit the module name$/, function () {
browser.waitForExist('#newSetModule', 5000);
browser.setValue('#newSetModule', module);
});
this.Then(/^he should be able to edit the module initials$/, function () {
browser.waitForExist('#newSetModuleShort', 5000);
browser.setValue('#newSetModuleShort', moduleInitials);
});
this.Then(/^he should be able to edit the module ID$/, function () {
browser.waitForExist('#newSetModuleNum', 5000);
browser.setValue('#newSetModuleNum', moduleID);
});
this.Then(/^he should be able to choose a college$/, function () {
browser.waitForVisible('#newSetCollege',5000);
browser.click('#newSetCollege');
browser.waitForVisible('li[data="' + college + '"] a', 5000);
browser.click('li[data="' + college + '"] a');
});
this.Then(/^he should be able to choose a course$/, function () {
browser.waitForVisible('#newSetCourse',5000);
browser.click('#newSetCourse');
browser.waitForVisible('li[data="' + course + '"] a', 5000);
browser.click('li[data="' + course + '"] a');
});