Index: .atoum.coverage.php
===================================================================
--- .atoum.coverage.php	(revision 2999)
+++ .atoum.coverage.php	(revision 2999)
@@ -0,0 +1,27 @@
+<?php
+# -- BEGIN LICENSE BLOCK ---------------------------------------
+#
+# This file is part of Dotclear 2.
+#
+# Copyright (c) 2003-2014 Olivier Meunier & Association Dotclear
+# Licensed under the GPL version 2.0 license.
+# See LICENSE file or
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+#
+# -- END LICENSE BLOCK -----------------------------------------
+
+use \mageekguy\atoum;
+
+// Write all on stdout.
+$stdOutWriter = new atoum\writers\std\out();
+
+// Generate a CLI report.
+$cliReport = new atoum\reports\realtime\cli();
+$cliReport->addWriter($stdOutWriter);
+
+// Coverage
+$coverageField = new atoum\report\fields\runner\coverage\html('Clearbricks', '/var/www/coverage/dotclear');
+$coverageField->setRootUrl('http://localhost/coverage/dotclear');
+$cliReport->addField($coverageField);
+
+$runner->addReport($cliReport);
Index: .atoum.php
===================================================================
--- .atoum.php	(revision 2999)
+++ .atoum.php	(revision 2999)
@@ -0,0 +1,23 @@
+<?php
+# -- BEGIN LICENSE BLOCK ---------------------------------------
+#
+# This file is part of Dotclear 2.
+#
+# Copyright (c) 2003-2014 Olivier Meunier & Association Dotclear
+# Licensed under the GPL version 2.0 license.
+# See LICENSE file or
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+#
+# -- END LICENSE BLOCK -----------------------------------------
+
+use \mageekguy\atoum;
+
+// Write all on stdout.
+$stdOutWriter = new atoum\writers\std\out();
+
+// Generate a CLI report.
+$cliReport = new atoum\reports\realtime\cli();
+$cliReport->addWriter($stdOutWriter);
+
+$runner->addTestsFromDirectory('tests/unit/');
+$runner->addReport($cliReport);
Index: Makefile
===================================================================
--- Makefile	(revision 2931)
+++ Makefile	(revision 2999)
@@ -22,4 +22,5 @@
 	       ./$(DC)/inc/libs/clearbricks/.atoum.* ./$(DC)/inc/libs/clearbricks/vendor	\
 	       ./$(DC)/inc/libs/clearbricks/bin ./$(DC)/inc/libs/clearbricks/_dist		\
+	       ./$(DC)/.atoum.* ./$(DC)/test ./$(DC)/travis					\
 	       ./$(DC)/features ./$(DC)/travis ./$(DC)/behat.yml.dist ./$(DC)/composer.*
 
Index: inc/core/class.dc.core.php
===================================================================
--- inc/core/class.dc.core.php	(revision 2753)
+++ inc/core/class.dc.core.php	(revision 2999)
@@ -573,5 +573,14 @@
 
 			if (!empty($params['order']) && !$count_only) {
-				$strReq .= 'ORDER BY '.$this->con->escape($params['order']).' ';
+				if (preg_match('`^([^. ]+) (?:asc|desc)`i',$params['order'],$matches)) {
+					if (in_array($matches[1],array('user_id','user_name','user_firstname','user_displayname'))) {
+						$table_prefix = 'U';
+					} else {
+						$table_prefix = 'P'; // order = nb_post (asc|desc)
+					}
+					$strReq .= 'ORDER BY '.$table_prefix.'.'.$this->con->escape($params['order']).' ';
+				} else {
+					$strReq .= 'ORDER BY '.$this->con->escape($params['order']).' ';
+				}
 			} else {
 				$strReq .= 'ORDER BY U.user_id ASC ';
Index: tests/unit/bootstrap.php
===================================================================
--- tests/unit/bootstrap.php	(revision 2999)
+++ tests/unit/bootstrap.php	(revision 2999)
@@ -0,0 +1,35 @@
+<?php
+# -- BEGIN LICENSE BLOCK ---------------------------------------
+#
+# This file is part of Dotclear 2.
+#
+# Copyright (c) 2003-2014 Olivier Meunier & Association Dotclear
+# Licensed under the GPL version 2.0 license.
+# See LICENSE file or
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+#
+# -- END LICENSE BLOCK -----------------------------------------
+
+require_once __DIR__.'/../../vendor/autoload.php';
+
+define('CLEARBRICKS_PATH',__DIR__.'/../../inc/libs/clearbricks');
+
+$__autoload = array();
+$__autoload['dbLayer'] = CLEARBRICKS_PATH.'/dblayer/dblayer.php';
+$__autoload['staticRecord'] = CLEARBRICKS_PATH.'/dblayer/dblayer.php';
+$__autoload['sqliteConnection'] = CLEARBRICKS_PATH.'/dblayer/class.sqlite.php';
+$__autoload['mysqlConnection'] = CLEARBRICKS_PATH.'/dblayer/class.mysql.php';
+$__autoload['mysqliConnection'] = CLEARBRICKS_PATH.'/dblayer/class.mysqli.php';
+$__autoload['pgsqlConnection'] = CLEARBRICKS_PATH.'/dblayer/class.pgsql.php';
+$__autoload['record'] = CLEARBRICKS_PATH.'/dblayer/dblayer.php';
+
+$__autoload['dcCore'] = __DIR__.'/../../inc/core/class.dc.core.php';
+$__autoload['dcNamespace'] = __DIR__.'/../../inc/core/class.dc.namespace.php';
+
+$loader = new \Composer\Autoload\ClassLoader();
+$loader->addClassMap($__autoload);
+$loader->register();
+
+function __($s) {
+    return $s;
+}
Index: tests/unit/inc/core/class.dc.core.php
===================================================================
--- tests/unit/inc/core/class.dc.core.php	(revision 2999)
+++ tests/unit/inc/core/class.dc.core.php	(revision 2999)
@@ -0,0 +1,114 @@
+<?php
+# -- BEGIN LICENSE BLOCK ---------------------------------------
+#
+# This file is part of Dotclear 2.
+#
+# Copyright (c) 2003-2014 Olivier Meunier & Association Dotclear
+# Licensed under the GPL version 2.0 license.
+# See LICENSE file or
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+#
+# -- END LICENSE BLOCK -----------------------------------------
+
+namespace tests\unit;
+
+use atoum;
+
+require_once __DIR__.'/../../bootstrap.php';
+$f = str_replace('\\','/',__FILE__);
+require_once(str_replace('tests/unit/','',$f));
+
+class dcCore extends atoum
+{
+    private $prefix = 'dc_';
+
+	private function getConnection($driver) {
+		$controller = new \atoum\mock\controller();
+		$controller->__construct = function() {};
+
+		$class_name = sprintf('\mock\%sConnection',$driver);
+		$con = new $class_name($driver,$controller);
+		$this->calling($con)->driver = $driver;
+		$this->calling($con)->escape = function($s) { // just for order, so don't care
+            return $s;
+        };
+		$this->calling($con)->select = function($sql) {
+            return new \staticRecord(array(),array());
+        };
+
+		return $con;
+	}
+
+    public function testGetUsers($driver) {
+        $con = $this->getConnection($driver);
+
+        $controller = new \atoum\mock\controller();
+        $controller->__construct = function() {};
+
+        $query = 'SELECT U.user_id,user_super,user_status,user_pwd,user_change_pwd,user_name,user_firstname,user_displayname,user_email,user_url,user_desc, user_lang,user_tz, user_post_status,user_options, count(P.post_id) AS nb_post FROM user U LEFT JOIN post P ON U.user_id = P.user_id WHERE NULL IS NULL GROUP BY U.user_id,user_super,user_status,user_pwd,user_change_pwd,user_name,user_firstname,user_displayname,user_email,user_url,user_desc, user_lang,user_tz,user_post_status,user_options ORDER BY U.user_id ASC ';
+
+        $core = new \mock\dcCore(null,null,null,null,null,null,null,$controller);
+        $core->con = $con;
+        $this
+			->if($core->getUsers())
+            ->then()
+				  ->mock($con)->call('select')
+				  ->withIdenticalArguments($query)
+				  ->once();
+    }
+
+    public function testGetUsersWithParams($driver,$params,$query) {
+        $con = $this->getConnection($driver);
+
+        $controller = new \atoum\mock\controller();
+        $controller->__construct = function() {};
+
+        $core = new \mock\dcCore(null,null,null,null,null,null,null,$controller);
+        $core->con = $con;
+
+        $this
+			->if($core->getUsers($params))
+            ->then()
+				  ->mock($con)->call('select')
+				  ->withIdenticalArguments($query)
+				  ->once();
+    }
+
+    /*
+     * DataProviders
+    **/
+    protected function testGetUsersDataProvider() {
+        $query = array();
+
+        return array(
+			array('pgsql'),
+			array('sqlite'),
+			array('mysql'),
+			array('mysqli'),
+		);
+    }
+
+    protected function testGetUsersWithParamsDataProvider() {
+        $base_query = 'SELECT U.user_id,user_super,user_status,user_pwd,user_change_pwd,user_name,user_firstname,user_displayname,user_email,user_url,user_desc, user_lang,user_tz, user_post_status,user_options, count(P.post_id) AS nb_post FROM user U LEFT JOIN post P ON U.user_id = P.user_id WHERE NULL IS NULL GROUP BY U.user_id,user_super,user_status,user_pwd,user_change_pwd,user_name,user_firstname,user_displayname,user_email,user_url,user_desc, user_lang,user_tz,user_post_status,user_options ORDER BY ';
+
+        return array(
+			array('pgsql',array('order' => 'user_id asc'),$base_query.'U.user_id asc '),
+			array('pgsql',array('order' => 'U.user_id asc'),$base_query.'U.user_id asc '),
+			array('mysql',array('order' => 'user_id asc'),$base_query.'U.user_id asc '),
+			array('mysql',array('order' => 'U.user_id asc'),$base_query.'U.user_id asc '),
+			array('mysqli',array('order' => 'user_id asc'),$base_query.'U.user_id asc '),
+			array('mysqli',array('order' => 'U.user_id asc'),$base_query.'U.user_id asc '),
+			array('sqlite',array('order' => 'user_id asc'),$base_query.'U.user_id asc '),
+			array('sqlite',array('order' => 'U.user_id asc'),$base_query.'U.user_id asc '),
+
+            array('pgsql',array('order' => 'nb_post desc'),$base_query.'P.nb_post desc '),
+			array('pgsql',array('order' => 'P.nb_post desc'),$base_query.'P.nb_post desc '),
+			array('mysql',array('order' => 'nb_post desc'),$base_query.'P.nb_post desc '),
+			array('mysql',array('order' => 'P.nb_post desc'),$base_query.'P.nb_post desc '),
+			array('mysqli',array('order' => 'nb_post desc'),$base_query.'P.nb_post desc '),
+			array('mysqli',array('order' => 'P.nb_post desc'),$base_query.'P.nb_post desc '),
+			array('sqlite',array('order' => 'nb_post desc'),$base_query.'P.nb_post desc '),
+			array('sqlite',array('order' => 'P.nb_post desc'),$base_query.'P.nb_post desc '),
+		);
+    }
+}
