Commit 631564b8 authored by Son Nguyen's avatar Son Nguyen
Browse files

Add new features for mahara behat



1. Data generator
2. Config manager
3. Form fixtures
4. Some sample test features

Add shell script for running Behat

Moving core feature files to 'test' folder outside the docroot

Fix Mysql bugs

Change-Id: I00f3558c178541ae7f81ce9fb1ce6226e7a9654e
Signed-off-by: default avatarAaron Barnes <aaronb@catalyst.net.nz>
Signed-off-by: default avatarSon Nguyen <son.nguyen@catalyst.net.nz>
parent d7485f0f
......@@ -14,6 +14,7 @@ mahara-*.zip
/test/.project
/test/.buildpath
/test/.settings
/test/behat/selenium-server-standalone*
/configure-stamp
/debian/files
/debian/mahara-apache.postinst.debhelper
......@@ -25,3 +26,4 @@ mahara-*.zip
/debian/mahara-apache2.substvars
/debian/mahara-apache2
.DS_Store
/external
{
"require": {
"php": ">=5.3.2",
"behat/behat": "2.5.1",
"behat/mink": "1.5.0",
"behat/behat": "~2.5",
"behat/mink": "*",
"behat/mink-extension": "*",
"behat/mink-goutte-driver": "*",
"behat/mink-selenium-driver": "*",
"behat/mink-selenium2-driver": "*"
"behat/mink-selenium2-driver": "*",
"fabpot/goutte": "~1.0"
},
"minimum-stability": "dev"
......
<?php
/**
* @package mahara
* @subpackage test/behat
* @author Son Nguyen, Catalyst IT Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later
* @copyright For copyright information on Mahara, please see the README file distributed with this software.
* @copyright portions from mahara Behat, 2013 David Monllaó
*
*/
require_once(dirname(dirname(dirname(__DIR__))) . '/testing/frameworks/behat/classes/BehatBase.php');
use Behat\Behat\Context\Step\Given as Given,
Behat\Gherkin\Node\TableNode as TableNode,
Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
/**
* Account steps definitions.
*
*/
class BehatAccount extends BehatBase {
/**
* Sets the specified account settings. A table with | Setting label | value | is expected.
*
* @Given /^the following account settings have set:$/
* @param TableNode $table
*/
public function i_set_the_following_account_settings_values(TableNode $table) {
// @TODO implement this step definition
}
}
\ No newline at end of file
@javasript @plugin @artefact.blog
Feature: Mahara users can create their blogs
As a mahara user
I need to create blogs
Scenario: create blogs
Given the following "users" exist:
| user | password | | institution | role |
| userA | Password1 | | mahara | member |
And the following account settings have set:
| Multiple journals | ON |
When I log in as "userA" with password "Password1"
And I follow "Content"
And I follow "Journal"
Then I should see "Journals"
When I press "Create journal"
And I fill in the following:
| title | My new journal |
| description | <p>This is my new journal</p> |
| tags | blog |
And I press "Create journal"
Then I should see "My new journal"
\ No newline at end of file
......@@ -338,7 +338,7 @@ function xmldb_core_upgrade($oldversion=0) {
delete_records('usr_watchlist_view','view',$viewid);
if ($blockinstanceids = get_column('block_instance', 'id', 'view', $viewid)) {
foreach ($blockinstanceids as $id) {
if (table_exists('blocktype_wall_post')) {
if (table_exists(new XMLDBTable('blocktype_wall_post'))) {
delete_records('blocktype_wall_post', 'instance', $id);
}
delete_records('view_artefact', 'block', $id);
......
......@@ -682,7 +682,9 @@ function uninstall_from_xmldb_file($file) {
if ($tables = array_reverse($structure->getTables())) {
foreach ($tables as $table) {
if ($indexes = $table->getIndexes()) {
// for MySQL, skip dropping indexs and keys
// as they will be dropped when the table is dropped
if (!is_mysql() && $indexes = $table->getIndexes()) {
foreach ($indexes as $index) {
if ($index->getName() == 'usernameuk' && is_postgres()) {
// this is a giant hack, but adodb cannot handle resolving
......@@ -694,7 +696,7 @@ function uninstall_from_xmldb_file($file) {
drop_index($table, $index);
}
}
if ($keys = $table->getKeys()) {
if (!is_mysql() && $keys = $table->getKeys()) {
$sortkeys = array();
foreach ($keys as $key) {
$sortkeys[] = $key->type;
......@@ -1413,63 +1415,189 @@ function rename_index($table, $index, $newname, $continue=true, $feedback=true)
}
/**
* Return all tables in current db
* Return structure info of tables from a xmldb file
*
* @return array('tablename' => 'tablename', ...)
* Note all table names is in lower cases
* @param string $file
* @return array(XMLDBTable)
* @throws InstallationException
*/
function get_tables() {
function get_tables_from_xmldb_file($file) {
global $CFG, $db;
// Get all tables in current DB
$tables = $metatables = $db->MetaTables('TABLES');
if (!empty($CFG->prefix)) {
$tables = array();
foreach ($metatables as $mtable) {
if (strpos($mtable, $CFG->prefix) !== false) {
$tables[] = $mtable;
$status = true;
$xmldb_file = new XMLDBFile($file);
if (!$xmldb_file->fileExists()) {
throw new InstallationException($xmldb_file->path . " doesn't exist.");
}
$loaded = $xmldb_file->loadXMLStructure();
if (!$loaded || !$xmldb_file->isLoaded()) {
throw new InstallationException("Could not load " . $xmldb_file->path);
}
$structure = $xmldb_file->getStructure();
return array_reverse($structure->getTables());
}
/**
* Return structure info of tables from mahara xmldb files
*
* @return array(XMLDBTable)
*/
function get_tables_from_xmldb() {
static $tables = array();
if (!empty($tables)) {
return $tables;
}
// Get database structure from plugins' tables
foreach (array_reverse(plugin_types_installed()) as $t) {
if ($installed = plugins_installed($t, true)) {
foreach ($installed as $p) {
$location = get_config('docroot') . $t . '/' . $p->name. '/db/';
if (is_readable($location . 'install.xml')) {
$tables = array_merge($tables, get_tables_from_xmldb_file($location . 'install.xml'));
}
}
}
}
unset($metatables);
$tnames = array();
foreach ($tables as $t) {
$t = strtolower($t);
$tnames[$t] = $t;
}
$tables = array_merge($tables, get_tables_from_xmldb_file(get_config('docroot') . 'lib/db/install.xml'));
return $tnames;
return $tables;
}
/**
* Return all columns of a table in current db
*
* @param string $tablename should be a full name including the dbprefix
* @param string $tablename not including the dbprefix
* @return array of ADOFieldObject
*/
function get_columns($tablename) {
global $CFG, $db;
$columns = $db->MetaColumns($tablename);
// Update the field Auto_increment if postgres
// Only apply for "id" field
if (is_postgres()) {
if (isset($columns['id'])) {
$idcolumn = $columns['id'];
if (isset($idcolumn->primary_key) && ($idcolumn->primary_key === 1)
&& isset($idcolumn->default_value)
&& strpos($idcolumn->default_value, 'nextval(') !== false ) {
$rec = get_record_sql('SELECT last_value FROM '. "{$tablename}" . '_id_seq');
$idcolumn->Auto_increment = $rec->last_value + 1;
$fulltablename = $CFG->dbprefix . $tablename;
$columns = $db->MetaColumns($fulltablename);
// Update the field auto_increment if postgres
// Only apply for "ID" field
if (is_postgres() && isset($columns['ID'])) {
$idcolumn = $columns['ID'];
if (isset($idcolumn->default_value)
&& strpos($idcolumn->default_value, 'nextval(') !== false ) {
if (record_exists($tablename)) {
$rec = get_record_sql('SELECT last_value FROM "' . $fulltablename . '_id_seq"');
$idcolumn->auto_increment = $rec->last_value + 1;
}
else {
$idcolumn->auto_increment = 1;
}
$columns['id'] = $idcolumn;
}
$columns['ID'] = $idcolumn;
}
return $columns;
}
/**
* Return current foreign key constraints in given table
*
* @param string $tablename not including the dbprefix
* @return array of array(
* 'constraintname' => string
* 'table' => string
* 'fields' => array
* 'reftable' => string
* 'reffields' => array
* )
*/
function get_foreign_keys($tablename) {
global $CFG;
$tablename = $CFG->dbprefix . $tablename;
$foreignkeys = array();
// Get foreign key constraints from information_schema tables
if (is_postgres()) {
$dbfield = 'catalog';
// The query to find all the columns for a foreign key constraint
$fkcolsql = "
SELECT
ku.column_name,
ccu.table_name AS reftable_name,
ccu.column_name AS refcolumn_name
FROM
information_schema.key_column_usage ku
INNER JOIN information_schema.constraint_column_usage ccu
ON ku.constraint_name = ccu.constraint_name
AND ccu.constraint_schema = ku.constraint_schema
AND ccu.constraint_catalog = ku.constraint_catalog
AND ccu.table_catalog = ku.constraint_catalog
AND ccu.table_schema = ku.constraint_schema
WHERE
ku.constraint_catalog = ?
AND ku.constraint_name = ?
AND ku.table_name = ?
AND ku.table_catalog = ?
ORDER BY ku.ordinal_position, ku.position_in_unique_constraint
";
}
else {
$dbfield = 'schema';
// The query to find all the columns for a foreign key constraint
$fkcolsql = '
SELECT
ku.column_name,
ku.referenced_table_name AS reftable_name,
ku.referenced_column_name AS refcolumn_name
FROM information_schema.key_column_usage ku
WHERE
ku.constraint_schema = ?
AND ku.constraint_name = ?
AND ku.table_name = ?
AND ku.table_schema = ?
ORDER BY ku.ordinal_position, ku.position_in_unique_constraint
';
}
$sql = "
SELECT tc.constraint_name
FROM information_schema.table_constraints tc
WHERE
tc.table_name = ?
AND tc.table_{$dbfield} = ?
AND tc.constraint_{$dbfield} = ?
AND tc.constraint_type = ?
";
$dbname = get_config('dbname');
if ($constraintrec = get_records_sql_array($sql, array($tablename, $dbname, $dbname, 'FOREIGN KEY'))) {
// Get foreign key constraint info
foreach ($constraintrec as $c) {
$fields = array();
$reftable = '';
$reffields = array();
if ($colrecs = get_records_sql_array($fkcolsql, array($dbname, $c->constraint_name, $tablename, $dbname))) {
foreach ($colrecs as $colrec) {
if (empty($reftable)) {
$reftable = $colrec->reftable_name;
}
$fields[] = $colrec->column_name;
$reffields[] = $colrec->refcolumn_name;
}
}
if (!empty($fields) && !empty($reftable) && !empty($reffields)) {
$foreignkeys[] = array(
'table' => $tablename,
'constraintname' => $c->constraint_name,
'fields' => $fields,
'reftable' => $reftable,
'reffields' => $reffields,
);
}
}
}
return $foreignkeys;
}
/**
* Return the server info
*
......
......@@ -612,7 +612,7 @@ class View {
return new View($view->get('id')); // Reread to ensure defaults are set
}
public function default_columnsperrow() {
public static function default_columnsperrow() {
$default = array(1 => (object)array('row' => 1, 'columns' => 3, 'widths' => '33,33,33'));
if (!$id = get_field('view_layout_columns', 'id', 'columns', $default[1]->columns, 'widths', $default[1]->widths)) {
throw new SystemException("View::default_columnsperrow: Default columns = 3, widths = '33,33,33' not in view_layout_columns table");
......
......@@ -164,7 +164,7 @@ class XMLDBObject {
function checkName () {
$result = true;
if ($this->name != eregi_replace('[^a-z0-9_ -]', '', $this->name)) {
if ($this->name != preg_replace('/[^a-z0-9_ -]/i', '', $this->name)) {
$result = false;
}
return $result;
......
......@@ -13,6 +13,8 @@
* Data generator class for unit tests and other tools like behat that need to create fake test sites.
*
*/
use Behat\Behat\Exception\UndefinedException as UndefinedException;
class TestingDataGenerator {
protected $usercounter = 0;
......@@ -82,8 +84,8 @@ EOD;
$classname = generate_generator_class_name($plugintype, $pluginname);
if (!class_exists($classname)) {
throw new SystemException("The plugin $pluginfullname does not support " .
"data generators yet. Class {$classname} not found.");
throw new UndefinedException("The plugin $pluginfullname does not support " .
"data generators yet. Class {$classname} not found.");
}
$this->generators[$pluginfullname] = new $classname($this);
......@@ -92,11 +94,309 @@ EOD;
}
/**
* Create a test user
* @param array|stdClass $record
* @param array $options
* @return stdClass user record
*/
public function create_user($record=null) {
* Create a test user
* @param array $record
* @throws SystemException if creating failed
* @return int new user id
*/
public function create_user($record) {
// Data validation
// Set default auth method for a new user is 'internal' for 'No institution' if not set
if (empty($record['institution']) || empty($record['authname'])) {
$record['institution'] = 'mahara';
$record['authname'] = 'internal';
}
if (!$auth = get_record('auth_instance', 'institution', $record['institution'], 'authname', $record['authname'])) {
throw new SystemException("The authentication method authname" . $record['authname'] . " for institution '" . $record['institution'] . "' does not exist.");
}
$record['authinstance'] = $auth->id;
// Don't exceed max user accounts for the institution
$institution = new Institution($record['institution']);
if ($institution->isFull()) {
throw new SystemException("Can not add new users to the institution '" . $record['institution'] . "' as it is full.");
}
$record['firstname'] = sanitize_firstname($record['firstname']);
$record['lastname'] = sanitize_lastname($record['lastname']);
$record['email'] = sanitize_email($record['email']);
$authobj = AuthFactory::create($auth->id);
if (method_exists($authobj, 'is_username_valid_admin') && !$authobj->is_username_valid_admin($record['username'])) {
throw new SystemException("New username'" . $record['username'] . "' is not valid.");
}
if (method_exists($authobj, 'is_username_valid') && !$authobj->is_username_valid($record['username'])) {
throw new SystemException("New username'" . $record['username'] . "' is not valid.");
}
if (record_exists_select('usr', 'LOWER(username) = ?', strtolower($record['username']))) {
throw new ErrorException("The username'" . $record['username'] . "' has been taken.");
}
if (method_exists($authobj, 'is_password_valid') && !$authobj->is_password_valid($record['password'])) {
throw new ErrorException("The password'" . $record['password'] . "' is not valid.");
}
if (record_exists('usr', 'email', $record['email'])
|| record_exists('artefact_internal_profile_email', 'email', $record['email'])) {
throw new ErrorException("The email'" . $record['email'] . "' has been taken.");
}
// Create new user
db_begin();
raise_time_limit(180);
$user = (object)array(
'authinstance' => $record['authinstance'],
'username' => $record['username'],
'firstname' => $record['firstname'],
'lastname' => $record['lastname'],
'email' => $record['email'],
'password' => $record['password'],
'passwordchange' => 0,
);
if ($record['institution'] == 'mahara') {
if ($record['role'] == 'admin') {
$user->admin = 1;
}
else if ($record['role'] == 'staff') {
$user->staff = 1;
}
}
$remoteauth = $record['authname'] != 'internal';
if (!isset($record['remoteusername'])) {
$record['remoteusername'] = null;
}
$user->id = create_user($user, array(), $record['institution'], $remoteauth, $record['remoteusername'], $record);
if (isset($user->admin) && $user->admin) {
require_once('activity.php');
activity_add_admin_defaults(array($user->id));
}
if ($record['institution'] != 'mahara') {
if ($record['role'] == 'admin') {
set_field('usr_institution', 'admin', 1, 'usr', $user->id, 'institution', $record['institution']);
}
else if ($record['role'] == 'staff') {
set_field('usr_institution', 'staff', 1, 'usr', $user->id, 'institution', $record['institution']);
}
}
db_commit();
$this->usercounter++;
return $user->id;
}
/**
* Create a test group
* @param array $record
* @throws ErrorException if creating failed
* @return int new group id
*/
public function create_group($record) {
// Data validation
$record['name'] = trim($record['name']);
if ($ids = get_records_sql_array('SELECT id FROM {group} WHERE LOWER(TRIM(name)) = ?', array(strtolower($record['name'])))) {
if (count($ids) > 1 || $ids[0]->id != $group_data->id) {
throw new SystemException("Invalid group name '" . $record['name'] . "'. " . get_string('groupalreadyexists', 'group'));
}
}
$record['owner'] = trim($record['owner']);
$ids = get_records_sql_array('SELECT id FROM {usr} WHERE LOWER(TRIM(username)) = ?', array(strtolower($record['owner'])));
if (!$ids || count($ids) > 1) {
throw new SystemException("Invalid group owner '" . $record['owner'] . "'. The username does not exist or duplicated");
}
$members = array($ids[0]->id => 'admin');
if (!empty($record['members'])) {
foreach (explode(',', $record['members']) as $membername) {
$ids = get_records_sql_array('SELECT id FROM {usr} WHERE LOWER(TRIM(username)) = ?', array(strtolower(trim($membername))));
if (!$ids || count($ids) > 1) {
throw new SystemException("Invalid group member '" . $membername . "'. The username does not exist or duplicated");
}
$members[$ids[0]->id] = 'member';
}
}
if (!empty($record['staff'])) {
foreach (explode(',', $record['staff']) as $membername) {
$ids = get_records_sql_array('SELECT id FROM {usr} WHERE LOWER(TRIM(username)) = ?', array(strtolower(trim($membername))));
if (!$ids || count($ids) > 1) {
throw new SystemException("Invalid group staff '" . $membername . "'. The username does not exist or duplicated");
}
$members[$ids[0]->id] = 'staff';
}
}
if (!empty($record['admins'])) {
foreach (explode(',', $record['admins']) as $membername) {
$ids = get_records_sql_array('SELECT id FROM {usr} WHERE LOWER(TRIM(username)) = ?', array(strtolower(trim($membername))));
if (!$ids || count($ids) > 1) {
throw new SystemException("Invalid group admin '" . $membername . "'. The username does not exist or duplicated");
}
$members[$ids[0]->id] = 'admin';
}
}
$availablegrouptypes = group_get_grouptypes();
if (!in_array($record['grouptype'], $availablegrouptypes)) {
throw new SystemException("Invalid grouptype '" . $record['grouptype'] . "'. This grouptype does not exist.\n"
. "The available grouptypes are " . join(', ', $availablegrouptypes));
}
$availablegroupeditroles = array_keys(group_get_editroles_options());
if (!in_array($record['editroles'], $availablegroupeditroles)) {
throw new SystemException("Invalid group editroles '" . $record['editroles'] . "'. This edit role does not exist.\n"
. "The available group editroles are " . join(', ', $availablegroupeditroles));
}
if (!empty($record['open'])) {
if (!empty($record['controlled'])) {
throw new SystemException('Invalid group membership setting. ' . get_string('membershipopencontrolled', 'group'));
}
if (!empty($record['request'])) {
throw new SystemException('Invalid group membership setting. ' . get_string('membershipopenrequest', 'group'));
}
}
if (!empty($record['invitefriends']) && !empty($record['suggestfriends'])) {
throw new SystemException('Invalid friend invitation setting. ' . get_string('suggestinvitefriends', 'group'));
}
if (!empty($record['suggestfriends']) && empty($record['open']) && empty($record['request'])) {
throw new SystemException('Invalid friend invitation setting. ' . get_string('suggestfriendsrequesterror', 'group'));
}
if (!empty($record['editwindowstart']) && !empty($record['editwindowend']) && ($record['editwindowstart'] >= $record['editwindowend'])) {
throw new SystemException('Invalid group editability setting. ' . get_string('editwindowendbeforestart', 'group'));
}
$group_data = array(
'id' => null,
'name' => $record['name'],
'description' => isset($record['description']) ? $record['description'] : null,
'grouptype' => $record['grouptype'],
'open' => isset($record['open']) ? $record['open'] : 1,
'controlled' => isset($record['controlled']) ? $record['controlled'] : 0,
'request' => isset($record['request']) ? $record['request'] : 0,
'invitefriends' => isset($record['invitefriends']) ? $record['invitefriends'] : 0,
'suggestfriends' => isset($record['suggestfriends']) ? $record['suggestfriends'] : 0,
'category' => null,
'public' => 0,
'usersautoadded' => 0,
'viewnotify' => GROUP_ROLES_ALL,
'submittableto' => isset($record['submittableto']) ? $record['submittableto'] : 0,
'allowarchives' => isset($record['allowarchives']) ? $record['allowarchives'] : 0,
'editroles' => isset($record['editroles']) ? $record['editroles'] : 'all',
'hidden' => 0,
'hidemembers' => 0,
'hidemembersfrommembers' => 0,
'groupparticipationreports' => 0,
'urlid' => null,
'editwindowstart' => isset($record['editwindowstart']) ? $record['editwindowstart'] : null,
'editwindowend' => isset($record['editwindowend']) ? $record['editwindowend'] : null,
'sendnow' => 0,
'feedbacknotify' => GROUP_ROLES_ALL,
'members' => $members,
);
// Create a new group