Browse Source

random lib and tokens table.

Mark Scherer 10 years ago
parent
commit
8fcc9bb6b1

+ 201 - 0
src/Model/Table/TokensTable.php

@@ -0,0 +1,201 @@
+<?php
+namespace Tools\Model\Table;
+
+use Tools\Model\Table\Table;
+use Tools\Utility\Random;
+use Cake\Utility\Hash;
+
+/**
+ * A generic model to hold tokens
+ *
+ * @author Mark Scherer
+ * @license http://opensource.org/licenses/mit-license.php MIT
+ */
+class TokensTable extends Table {
+
+	public $displayField = 'key';
+
+	public $order = ['created' => 'DESC'];
+
+	public $defaultLength = 22;
+
+	public $validity = MONTH;
+
+	public $validate = [
+		'type' => [
+			'notBlank' => [
+				'rule' => 'notBlank',
+				'message' => 'valErrMandatoryField',
+			],
+		],
+		'key' => [
+			'notBlank' => [
+				'rule' => ['notBlank'],
+				'message' => 'valErrMandatoryField',
+				'last' => true,
+			],
+			'isUnique' => [
+				'rule' => ['isUnique'],
+				'message' => 'valErrTokenExists',
+			],
+		],
+		'content' => [
+			'maxLength' => [
+				'rule' => ['maxLength', 255],
+				//'message' => ['valErrMaxCharacters %s', 255],
+				'allowEmpty' => true
+			],
+		],
+		'used' => ['numeric']
+	];
+
+	/**
+	 * Stores new key in DB
+	 *
+	 * @param string type: necessary
+	 * @param string key: optional key, otherwise a key will be generated
+	 * @param mixed user_id: optional (if used, only this user can use this key)
+	 * @param string content: up to 255 characters of content may be added (optional)
+	 * NOW: checks if this key is already used (should be unique in table)
+	 * @return string key on SUCCESS, boolean false otherwise
+	 */
+	public function newKey($type, $key = null, $uid = null, $content = null) {
+		if (empty($type)) {
+			return false;
+		}
+
+		if (empty($key)) {
+			$key = $this->generateKey($this->defaultLength);
+			$keyLength = $this->defaultLength;
+		} else {
+			$keyLength = mb_strlen($key);
+		}
+
+		$data = [
+			'type' => $type,
+			'user_id' => (string)$uid,
+			'content' => (string)$content,
+			'key' => $key,
+		];
+
+		$entity = $this->newEntity($data);
+		$max = 99;
+		while (!$this->save($entity)) {
+			$entity['key'] = $this->generateKey($keyLength);
+			$max--;
+			if ($max === 0) {
+				return false;
+			}
+		}
+
+		return $entity['key'];
+	}
+
+	/**
+	 * UsesKey (only once!) - by KEY
+	 *
+	 * @param string type: necessary
+	 * @param string key: necessary
+	 * @param mixed user_id: needs to be provided if this key has a user_id stored
+	 * @return array Content - if successfully used or if already used (used=1), FALSE else
+	 */
+	public function useKey($type, $key, $uid = null, $treatUsedAsInvalid = false) {
+		if (empty($type) || empty($key)) {
+			return false;
+		}
+		$options = ['conditions' => [$this->alias() . '.key' => $key, $this->alias() . '.type' => $type]];
+		if (!empty($uid)) {
+			$options['conditions'][$this->alias() . '.user_id'] = $uid;
+		}
+		$res = $this->find('first', $options);
+		if (empty($res)) {
+			return false;
+		}
+		if (!empty($uid) && !empty($res['user_id']) && $res['user_id'] != $uid) {
+			// return $res; # more secure to fail here if user_id is not provided, but was submitted prev.
+			return false;
+		}
+		// already used?
+		if (!empty($res['used'])) {
+			if ($treatUsedAsInvalid) {
+				return false;
+			}
+			// return true and let the application check what to do then
+			return $res;
+		}
+		// actually spend key (set to used)
+		if ($this->spendKey($res['id'])) {
+			return $res;
+		}
+		// no limit? we dont spend key then
+		if (!empty($res['unlimited'])) {
+			return $res;
+		}
+		$this->log('VIOLATION in ' . $this->alias() . ' Model (method useKey)');
+		return false;
+	}
+
+	/**
+	 * Sets Key to "used" (only once!) - directly by ID
+	 *
+	 * @param id of key to spend: necessary
+	 * @return bool Success
+	 */
+	public function spendKey($id) {
+		if (empty($id)) {
+			return false;
+		}
+
+		//$expression = new \Cake\Database\Expression\QueryExpression(['used = used + 1', 'modified' => date(FORMAT_DB_DATETIME)]);
+		if ($x = $this->updateAll(
+			['used = used + 1', 'modified' => date(FORMAT_DB_DATETIME)],
+			[$this->alias() . '.id' => $id])
+		) {
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Remove old/invalid keys
+	 * does not remove recently used ones (for proper feedback)!
+	 *
+	 * @return bool success
+	 */
+	public function garbageCollector() {
+		$conditions = [
+			$this->alias() . '.created <' => date(FORMAT_DB_DATETIME, time() - $this->validity),
+		];
+		return $this->deleteAll($conditions, false);
+	}
+
+	/**
+	 * Get admin stats
+	 */
+	public function stats() {
+		$keys = [];
+		$keys['unused_valid'] = $this->find('count', ['conditions' => [$this->alias() . '.used' => 0, $this->alias() . '.created >=' => date(FORMAT_DB_DATETIME, time() - $this->validity)]]);
+		$keys['used_valid'] = $this->find('count', ['conditions' => [$this->alias() . '.used' => 1, $this->alias() . '.created >=' => date(FORMAT_DB_DATETIME, time() - $this->validity)]]);
+
+		$keys['unused_invalid'] = $this->find('count', ['conditions' => [$this->alias() . '.used' => 0, $this->alias() . '.created <' => date(FORMAT_DB_DATETIME, time() - $this->validity)]]);
+		$keys['used_invalid'] = $this->find('count', ['conditions' => [$this->alias() . '.used' => 1, $this->alias() . '.created <' => date(FORMAT_DB_DATETIME, time() - $this->validity)]]);
+
+		$types = $this->find('all', ['conditions' => [], 'fields' => ['DISTINCT type']]);
+		$keys['types'] = !empty($types) ? Hash::extract('{n}.type', $types) : [];
+		return $keys;
+	}
+
+	/**
+	 * Generator
+	 *
+	 * @param length (defaults to defaultLength)
+	 * @return string Key
+	 */
+	public function generateKey($length = null) {
+		if (empty($length)) {
+			$length = $this->defaultLength;
+		}
+		return Random::pwd($length);
+	}
+
+}

+ 312 - 0
src/Utility/Random.php

@@ -0,0 +1,312 @@
+<?php
+namespace Tools\Utility;
+
+/**
+ * Random Lib
+ *
+ * @author Mark Scherer
+ * @license http://opensource.org/licenses/mit-license.php MIT
+ */
+class Random {
+
+	/**
+	 * @param int $min
+	 * @param int $max
+	 * @return random int value
+	 */
+	public static function int($min = 0, $max = 999) {
+		return mt_rand($min, $max);
+	}
+
+	/**
+	 * @param float $min
+	 * @param float $max
+	 * @return random float value
+	 */
+	public static function float($min = 0.0, $max = 999.0) {
+		$rand = rand(1, 358);
+		return $rand * cos($rand);
+	}
+
+	/**
+	 * Randomly return one of the values provided
+	 * careful: only works with numerical keys (0 based!)
+	 *
+	 * @return mixed
+	 */
+	public static function arrayValue($array, $minPosition = null, $maxPosition = null, $integerKeys = false) {
+		if (empty($array)) {
+			return null;
+		}
+		if ($integerKeys) {
+			$max = count($array) - 1;
+			return $array[static::int(0, $max)];
+		}
+		$keys = array_keys($array);
+		$values = array_values($array);
+		$max = count($keys) - 1;
+		return $values[static::int(0, $max)];
+	}
+
+	public static function arrayValues($array, $minAmount = null, $maxAmount = null) {
+		//NOT IMPORTANT
+	}
+
+	/**
+	 * 1950-01-01 - 2050-12-31
+	 */
+	public static function date($min = null, $max = null, $formatReturn = null) {
+		if ($min === null && $max === null) {
+			$res = time();
+		} elseif ($min > 0 && $max === null) {
+			$res = $min;
+		} elseif ($min > 0 && $max > 0) {
+			$res = static::int($min, $max);
+		} else {
+			$res = time();
+		}
+
+		$res = 0;
+		$formatReturnAs = FORMAT_DB_DATETIME;
+		if ($formatReturn !== null) {
+			if ($formatReturn === false) {
+				return $res;
+			}
+			$formatReturnAs = $formatReturn;
+		}
+		return date($formatReturnAs);
+	}
+
+	//TODO
+	/**
+	 * 00:00:00 - 23:59:59
+	 */
+	public static function time($min = null, $max = null, $formatReturn = null) {
+		$res = 0;
+		//$returnValueAs = FORMAT_DB_TIME;
+		if ($formatReturn !== null) {
+			if ($formatReturn === false) {
+				return $res;
+			}
+		}
+	}
+
+	/**
+	 * Returns a date of birth within the specified age range
+	 *
+	 * @param (int) $min minimum age in years
+	 * @param (int) $max maximum age in years
+	 * @return (string) $dob a db (ISO) format datetime string
+	 */
+	public static function dob($min = 18, $max = 100) {
+		$dobYear = date('Y') - (static::int($min, $max));
+
+		$dobMonth = static::int(1, 12);
+
+		if ($dobMonth == 2) {
+			// leap year?
+			if ($dobYear % 4 || $dobYear % 400) {
+				$maxDays = 29;
+			} else {
+				$maxDays = 28;
+			}
+		} elseif (in_array($dobMonth, [4, 6, 9, 11])) {
+			$maxDays = 30;
+		} else {
+			$maxDays = 31;
+		}
+
+		$dobDay = static::int(1, $maxDays);
+
+		$dob = sprintf("%4d-%02d-%02d", $dobYear, $dobMonth, $dobDay);
+		return $dob;
+	}
+
+	/**
+	 * Generates a password
+	 *
+	 * @param int $length Password length
+	 * @return string
+	 * @link https://github.com/CakeDC/users/blob/master/models/user.php#L498
+	 */
+	public static function pronounceablePwd($length = 10) {
+		srand((double)microtime() * 1000000);
+		$password = '';
+		$vowels = ["a", "e", "i", "o", "u"];
+		$cons = ["b", "c", "d", "g", "h", "j", "k", "l", "m", "n", "p", "r", "s", "t", "u", "v", "w", "tr",
+							"cr", "br", "fr", "th", "dr", "ch", "ph", "wr", "st", "sp", "sw", "pr", "sl", "cl"];
+		for ($i = 0; $i < $length; $i++) {
+			$password .= $cons[mt_rand(0, 31)] . $vowels[mt_rand(0, 4)];
+		}
+		return substr($password, 0, $length);
+	}
+
+	/**
+	 * Generates random passwords.
+	 *
+	 * @param int $length (necessary!)
+	 * @return string Password
+	 */
+	public static function pwd($length, $chars = null) {
+		if ($chars === null) {
+			$chars = '234567890abcdefghijkmnopqrstuvwxyz'; // ABCDEFGHIJKLMNOPQRSTUVWXYZ
+		}
+		$i = 0;
+		$password = '';
+		$max = strlen($chars) - 1;
+
+		while ($i < $length) {
+			$password .= $chars[mt_rand(0, $max)];
+			$i++;
+		}
+		return $password;
+	}
+
+	/**
+	 * Few years ago i had written Joomla component AutoContent. That component generates new unique content and add it to Joomla site. In general, this is not good tool, because it does black SEO things. If search engine (ex. Google) finds that there is autogenerated text without sense then site can be banned
+	 *
+	 * @link http://www.gelembjuk.com/index.php?option=com_content&view=article&id=60&catid=37:php&Itemid=56
+	 */
+	public static function sentences($sentences, $wordscount = 2) {
+		$hash = [];
+		$resultsentences = [];
+
+		for ($i = 0; $i < count($sentences); $i++) {
+			$words = split(' ', trim($sentences[$i]));
+
+			for ($k = 0; $k < count($words) - $wordscount; $k++) {
+				$prefix = trim(implode(' ', array_slice($words, $k, $wordscount)));
+				if ($prefix === '') {
+					continue;
+				}
+
+				if (empty($hash[$prefix])) {
+					$hash[$prefix] = [$words[$k + $wordscount]];
+
+					for ($j = $i + 1; $j < count($sentences); $j++) {
+						if (preg_match('/' . ereg_replace('/', '\/', preg_quote($prefix)) . '(.*)$/', $sentences[$j], $m)) {
+							$w = split(' ', trim($m[1]));
+
+							if (count($w) > 0 && trim($w[0]) !== '') {
+								array_push($hash[$prefix], trim($w[0]));
+							}
+						}
+					}
+				}
+			}
+		}
+
+		$prefixes = array_keys($hash);
+
+		$stpr = [];
+		foreach ($prefixes as $pr) {
+			if ($pr[0] == strtoupper($pr[0])) {
+				array_push($stpr, $pr);
+			}
+		}
+
+		for ($i = 0; $i < count($sentences); $i++) {
+			$p = $stpr[rand(0, count($stpr) - 1)];
+			$sent = split(' ', $p);
+			$cc = count(split(' ', $sentences[$i]));
+
+			$j = 0;
+
+			do {
+				$w = $hash[$p][rand(0, count($hash[$p]) - 1)];
+				array_push($sent, $w);
+				$j++;
+
+				$p = implode(' ', array_slice($sent, $j, $wordscount));
+			} while (strrpos($w, '.') != strlen($w) - 1 && $j < $cc * 2);
+
+			$sn = implode(' ', $sent);
+			if ($sn[strlen($sn) - 1] !== '.') {
+				$sn .= '.';
+			}
+			array_push($resultsentences, $sn);
+		}
+		return $resultsentences;
+	}
+
+	/**
+	 * Other implemantations
+	 */
+
+	/**
+	 * Generates a random string of a given type and length.
+	 * $str = Text::random(); // 8 character random string
+	 *
+	 * The following types are supported:
+	 *
+	 * alnum
+	 * : Upper and lower case a-z, 0-9
+	 *
+	 * alpha
+	 * : Upper and lower case a-z
+	 *
+	 * hexdec
+	 * : Hexadecimal characters a-f, 0-9
+	 *
+	 * distinct
+	 * : Uppercase characters and numbers that cannot be confused
+	 *
+	 * You can also create a custom type by providing the "pool" of characters
+	 * as the type.
+	 *
+	 * @param string a type of pool, or a string of characters to use as the pool
+	 * @param int length of string to return
+	 * @return string
+	 */
+	public static function generate($type = 'alnum', $length = 8) {
+		switch ($type) {
+			case 'alnum':
+				$pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+				break;
+			case 'alpha':
+				$pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+				break;
+			case 'hexdec':
+				$pool = '0123456789abcdef';
+				break;
+			case 'numeric':
+				$pool = '0123456789';
+				break;
+			case 'nozero':
+				$pool = '123456789';
+				break;
+			case 'distinct':
+				$pool = '2345679ACDEFHJKLMNPRSTUVWXYZ';
+				break;
+			default:
+				$pool = (string)$type;
+				break;
+		}
+
+		// Split the pool into an array of characters
+		$pool = str_split($pool, 1);
+
+		// Largest pool key
+		$max = count($pool) - 1;
+
+		$str = '';
+		for ($i = 0; $i < $length; $i++) {
+			// Select a random character from the pool and add it to the string
+			$str .= $pool[mt_rand(0, $max)];
+		}
+
+		// Make sure alnum strings contain at least one letter and one digit
+		if ($type === 'alnum' && $length > 1) {
+			if (ctype_alpha($str)) {
+				// Add a random digit
+				$str[mt_rand(0, $length - 1)] = chr(mt_rand(48, 57));
+			} elseif (ctype_digit($str)) {
+				// Add a random letter
+				$str[mt_rand(0, $length - 1)] = chr(mt_rand(65, 90));
+			}
+		}
+
+		return $str;
+	}
+
+}

+ 136 - 0
tests/Fixture/TokensFixture.php

@@ -0,0 +1,136 @@
+<?php
+namespace Tools\Test\Fixture;
+
+use Cake\TestSuite\Fixture\TestFixture;
+
+/**
+ * TokenFixture
+ *
+ */
+class TokensFixture extends TestFixture {
+
+	/**
+	 * Fields
+	 *
+	 * @var array
+	 */
+	public $fields = [
+		'id' => ['type' => 'integer', 'null' => false, 'default' => null, 'length' => 10],
+		'user_id' => ['type' => 'string', 'null' => true, 'length' => 36, 'collate' => 'utf8_unicode_ci', 'comment' => '', 'charset' => 'utf8'],
+		'type' => ['type' => 'string', 'null' => true, 'default' => null, 'length' => 10, 'collate' => 'utf8_unicode_ci', 'comment' => 'e.g.:activate,reactivate', 'charset' => 'utf8'],
+		'key' => ['type' => 'string', 'null' => true, 'default' => null, 'length' => 60, 'collate' => 'utf8_unicode_ci', 'comment' => '', 'charset' => 'utf8'],
+		'content' => ['type' => 'string', 'null' => true, 'default' => null, 'collate' => 'utf8_unicode_ci', 'comment' => 'can transport some information', 'charset' => 'utf8'],
+		'used' => ['type' => 'integer', 'null' => false, 'default' => '0', 'collate' => null, 'comment' => ''],
+		'created' => ['type' => 'datetime', 'null' => true, 'default' => null, 'collate' => null, 'comment' => ''],
+		'modified' => ['type' => 'datetime', 'null' => true, 'default' => null, 'collate' => null, 'comment' => ''],
+		'_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]]
+	];
+
+	/**
+	 * Records
+	 *
+	 * @var array
+	 */
+	public $records = [
+		[
+			'id' => '77',
+			'user_id' => '1',
+			'type' => 'qlogin',
+			'key' => '7k8qdcizigtudvxn2v9zep',
+			'content' => 'i:1;',
+			'used' => 0,
+			'created' => '2011-08-02 18:00:41',
+			'modified' => '2011-08-02 18:00:41'
+		],
+		[
+			'id' => '78',
+			'user_id' => '2',
+			'type' => 'qlogin',
+			'key' => '23e32tpkcmdn8x9j8n0n00',
+			'content' => 'i:2;',
+			'used' => 0,
+			'created' => '2011-08-02 18:00:41',
+			'modified' => '2011-08-02 18:00:41'
+		],
+		[
+			'id' => '79',
+			'user_id' => '1',
+			'type' => 'qlogin',
+			'key' => '3mpzed7eoewsjvyvg4vy35',
+			'content' => 'a:3:{s:10:"controller";s:4:"test";s:6:"action";s:3:"foo";i:0;s:3:"bar";}',
+			'used' => 1,
+			'created' => '2011-08-02 18:00:41',
+			'modified' => '2011-08-02 18:00:41'
+		],
+		[
+			'id' => '80',
+			'user_id' => '2',
+			'type' => 'qlogin',
+			'key' => 'af8ww4y7jxzq5n6npmjpxx',
+			'content' => 's:13:"/test/foo/bar";',
+			'used' => 1,
+			'created' => '2011-08-02 18:00:41',
+			'modified' => '2011-08-02 18:00:41'
+		],
+		[
+			'id' => '81',
+			'user_id' => '1',
+			'type' => 'qlogin',
+			'key' => '2s7i3zjw0rn009j4no552b',
+			'content' => 'i:1;',
+			'used' => 0,
+			'created' => '2011-08-02 18:01:16',
+			'modified' => '2011-08-02 18:01:16'
+		],
+		[
+			'id' => '82',
+			'user_id' => '2',
+			'type' => 'qlogin',
+			'key' => 'tro596dig63cay0ps09vre',
+			'content' => 'i:2;',
+			'used' => 0,
+			'created' => '2011-08-02 18:01:16',
+			'modified' => '2011-08-02 18:01:16'
+		],
+		[
+			'id' => '83',
+			'user_id' => '1',
+			'type' => 'qlogin',
+			'key' => 'penfangwc40x550wwvgfmu',
+			'content' => 'a:3:{s:10:"controller";s:4:"test";s:6:"action";s:3:"foo";i:0;s:3:"bar";}',
+			'used' => 1,
+			'created' => '2011-08-02 18:01:16',
+			'modified' => '2011-08-02 18:01:16'
+		],
+		[
+			'id' => '84',
+			'user_id' => '2',
+			'type' => 'qlogin',
+			'key' => '2y7m5srasm3ozej0izxbhe',
+			'content' => 's:13:"/test/foo/bar";',
+			'used' => 1,
+			'created' => '2011-08-02 18:01:16',
+			'modified' => '2011-08-02 18:01:16'
+		],
+		[
+			'id' => '85',
+			'user_id' => '1',
+			'type' => 'qlogin',
+			'key' => '5c6dp2w54ynxii2xo3c50m',
+			'content' => 'i:1;',
+			'used' => 0,
+			'created' => '2011-08-02 18:01:54',
+			'modified' => '2011-08-02 18:01:54'
+		],
+		[
+			'id' => '86',
+			'user_id' => '2',
+			'type' => 'qlogin',
+			'key' => 'fr6a0d4waue2v6hmqeyek5',
+			'content' => 'i:2;',
+			'used' => 0,
+			'created' => '2011-08-02 18:01:54',
+			'modified' => '2011-08-02 18:01:54'
+		],
+	];
+}

+ 63 - 0
tests/TestCase/Model/Table/TokensTableTest.php

@@ -0,0 +1,63 @@
+<?php
+namespace Tools\Test\Model\Table;
+
+use Tools\Model\Table\TokensTable;
+use Tools\TestSuite\TestCase;
+use Cake\ORM\TableRegistry;
+
+class TokensTableTest extends TestCase {
+
+	public $Tokens = null;
+
+	public $fixtures = ['plugin.Tools.Tokens'];
+
+	public function setUp() {
+		parent::setUp();
+
+		$this->Tokens = TableRegistry::get('Tools.Tokens');
+	}
+
+	public function testTokenInstance() {
+		$this->assertInstanceOf('Tools\Model\Table\TokensTable', $this->Tokens);
+	}
+
+	public function testGenerateKey() {
+		$key = $this->Tokens->generateKey(4);
+		//pr($key);
+		$this->assertTrue(!empty($key) && strlen($key) === 4);
+	}
+
+	public function testNewKeySpendKey() {
+		$key = $this->Tokens->newKey('test', null, null, 'xyz');
+		$this->assertTrue(!empty($key));
+
+		$res = $this->Tokens->useKey('test', $key);
+		//pr($res);
+		$this->assertTrue(!empty($res));
+
+		$res = $this->Tokens->useKey('test', $key);
+		//pr($res);
+		$this->assertTrue(!empty($res) && !empty($res['used']));
+
+		$res = $this->Tokens->useKey('test', $key . 'x');
+		$this->assertFalse($res);
+
+		$res = $this->Tokens->useKey('testx', $key);
+		$this->assertFalse($res);
+	}
+
+	public function testGarbageCollector() {
+		$data = [
+			'created' => date(FORMAT_DB_DATETIME, time() - 3 * MONTH),
+			'type' => 'y',
+			'key' => 'x'
+		];
+		$entity = $this->Tokens->newEntity($data, ['validate' => false]);
+		$this->Tokens->save($entity);
+		$count = $this->Tokens->find('count');
+		$this->Tokens->garbageCollector();
+		$count2 = $this->Tokens->find('count');
+		$this->assertTrue($count > $count2);
+	}
+
+}

+ 51 - 0
tests/TestCase/Utility/RandomTest.php

@@ -0,0 +1,51 @@
+<?php
+namespace Tools\TestCase\Utility;
+
+use Tools\Utility\Random;
+use Tools\TestSuite\TestCase;
+
+class RandomTest extends TestCase {
+
+	public function testInt() {
+		$is = Random::int(2, 200);
+		//pr($is);
+		$this->assertTrue($is >= 2 && $is <= 200);
+	}
+
+	public function testArrayValue() {
+		$array = [
+			'x',
+			'y',
+			'z',
+		];
+		$is = Random::arrayValue($array, null, null, true);
+		$this->assertTrue(in_array($is, $array));
+
+		// non-numerical indexes
+		$array = [
+			'e' => 'x',
+			'f' => 'y',
+			'g' => 'z',
+		];
+		$is = Random::arrayValue($array);
+		$this->assertTrue(in_array($is, $array));
+	}
+
+	public function testPwd() {
+		$result = Random::pwd(10);
+		$this->assertTrue(mb_strlen($result) === 10);
+	}
+
+	public function testPronounceablePwd() {
+		$is = Random::pronounceablePwd(6);
+		//pr($is);
+		$this->assertTrue(strlen($is) === 6);
+
+		$is = Random::pronounceablePwd(11);
+		//pr($is);
+		$this->assertTrue(strlen($is) === 11);
+	}
+
+	//TOOD: other tests
+
+}