Skip to content

Commit

Permalink
added new 'validate' controller and hooked it into CI
Browse files Browse the repository at this point in the history
Partly satisfies #81
  • Loading branch information
RocketMan committed Sep 13, 2021
1 parent 64b2845 commit b5a8fb5
Show file tree
Hide file tree
Showing 9 changed files with 297 additions and 11 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,4 @@ before_script:
- export HTTP_ACCEPT="application/json"
- export HTTP_USER_AGENT="Travis CI"

script: php zk test action=test subaction=test
script: php zk validate
1 change: 1 addition & 0 deletions config/controller_config.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@
'api' => ZK\Controllers\API::class,
'sso' => ZK\Controllers\SSOLogin::class,
'push' => ZK\Controllers\PushServer::class,
'validate' => ZK\Controllers\Validate::class,
];
10 changes: 5 additions & 5 deletions controllers/API.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ public function emitDataSetArray($name, $fields, &$data) {
echo "$nextToken{";
$nextProp = "";
foreach($fields as $field) {
$val = $row[$field];
$val = $row[$field] ?? "";
if($name == "albumrec") {
switch($field) {
case "category":
Expand Down Expand Up @@ -220,7 +220,7 @@ public function emitDataSetArray($name, $fields, &$data) {
foreach($data as $row) {
echo "<$name>\n";
foreach($fields as $field) {
$val = $row[$field];
$val = $row[$field] ?? "";
if($name == "albumrec") {
switch($field) {
case "category":
Expand Down Expand Up @@ -361,7 +361,7 @@ class API extends CommandTarget implements IController {
private $serializer;

public function processRequest() {
$wantXml = $_REQUEST["xml"] ||
$wantXml = $_REQUEST["xml"] ?? false || isset($_SERVER["HTTP_ACCEPT"]) &&
substr($_SERVER["HTTP_ACCEPT"], 0, 8) == "text/xml";
$this->serializer = $wantXml?new XMLSerializer():new JSONSerializer();

Expand Down Expand Up @@ -623,7 +623,7 @@ public function getCurrents() {
}

private function emitHeader($method) {
$preflight = $_SERVER['REQUEST_METHOD'] == "OPTIONS";
$preflight = ($_SERVER['REQUEST_METHOD'] ?? null) == "OPTIONS";
if($preflight)
http_response_code(204); // 204 No Content

Expand All @@ -632,7 +632,7 @@ private function emitHeader($method) {
// Go ahead and give the Content-type in all cases.
header("Content-type: ". $this->serializer->getContentType());

$origin = $_SERVER['HTTP_ORIGIN'];
$origin = $_SERVER['HTTP_ORIGIN'] ?? null;
if($origin) {
if($method && in_array($method, self::$publicMethods)) {
header("Access-Control-Allow-Origin: *");
Expand Down
258 changes: 258 additions & 0 deletions controllers/Validate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
<?php
/**
* Zookeeper Online
*
* @author Jim Mason <[email protected]>
* @copyright Copyright (C) 1997-2021 Jim Mason <[email protected]>
* @link https://zookeeper.ibinx.com/
* @license GPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License,
* version 3, along with this program. If not, see
* http://www.gnu.org/licenses/
*
*/

namespace ZK\Controllers;

use ZK\Engine\Engine;
use ZK\Engine\IChart;
use ZK\Engine\IDJ;
use ZK\Engine\IPlaylist;
use ZK\Engine\IUser;
use ZK\Engine\PlaylistEntry;

use ZK\UI\UICommon as UI;

class Validate implements IController {
private $success = true;
private $session;
private $testUser;
private $testPass;

private const TEST_NAME = "TEST User";
private const TEST_ACCESS = "qr"; // some unused roles for safety
private const TEST_COMMENT = "TEST comment!";
private const TEST_TRACK = "TEST Grommet Track"; // second word is search key

private const FAIL = "\033[0;31m";
private const OK = "\033[0;32m";
private const SKIP = "\033[0;33m";
private const NORMAL = "\033[0m";

private function doTest($name, $runTest = null) {
if($runTest === null)
$runTest = $this->success;

echo "\t${name}: ";
if(!$runTest)
echo self::SKIP."SKIPPED".self::NORMAL."\n";
return $runTest;
}

private function showSuccess($success, $critical = true) {
if($critical)
$this->success &= $success;

if($success)
echo self::OK."OK";
else {
echo self::FAIL."FAILED!";
if(!$critical)
echo " (soft failure)";
}
echo self::NORMAL."\n";
}

public function processRequest() {
if(php_sapi_name() != "cli") {
http_response_code(400);
return;
}

$this->session = Engine::session();
echo "\nStarting Validation...\n\n";
try {
$this->validateCreateUser();
$this->validateSignon();
$this->validatePlaylists();
$this->validateCategories();
$this->validateDeleteUser();
} catch (\Exception $e) {
// if there is a db configuration issue (wrong db name
// or password, etc.), we'll get an exception.
echo self::FAIL."\nFATAL: ".$e->getMessage().self::NORMAL."\n";
$this->success = false;
}
echo "\nDone.\n";
exit($this->success?0:1);
}

public function validateCreateUser() {
$api = Engine::api(IUser::class);

$this->doTest("create user");
$this->testUser = "__".substr(md5(uniqid(rand())), 0, 6);
$this->testPass = md5(uniqid(rand()));
$success = $api->insertUser($this->testUser, $this->testPass,
self::TEST_NAME, self::TEST_ACCESS, "");
$this->showSuccess($success);

if($this->doTest("validate user", $success)) {
$user = $api->getUser($this->testUser);
$success2 = $user['realname'] == self::TEST_NAME;
$this->showSuccess($success2);
}

if(!$success) {
error_reporting(0);
$api->deleteUser($this->testUser);
error_reporting(E_ALL & ~E_NOTICE);

$this->testUser = null;
}
}

public function validateSignon() {
if($this->doTest("validate signon")) {
$this->showSuccess(
Engine::api(IUser::class)->validatePassword(
$this->testUser, $this->testPass, 1, $access));
}

if($this->doTest("validate session")) {
// Suppress warnings from session cookie creation
error_reporting(E_ERROR);
$sessionID = md5(uniqid(rand()));
$this->session->create($sessionID, $this->testUser, $access);

// Validate session
$this->session->validate($sessionID);
$success = $this->session->isAuth(substr(self::TEST_ACCESS, 1, 1));
$this->showSuccess($success);

// Resume normal error reporting
error_reporting(E_ALL & ~E_NOTICE);
}
}

public function validatePlaylists() {
if($this->doTest("create airname")) {
$djapi = Engine::api(IDJ::class);
$airname = self::TEST_NAME." ".$this->testUser; // make unique
$success = $djapi->insertAirname($airname, $this->testUser);
$this->showSuccess($success);
} else
$success = false;

if($this->doTest("create playlist", $success)) {
$aid = $djapi->lastInsertId();
$papi = Engine::api(IPlaylist::class);
$success = $papi->insertPlaylist($this->testUser, "2020-01-01", "1200-1400", "TEST Show", $aid);
if($success)
$pid = $papi->lastInsertId();
$this->showSuccess($success);
}

if($this->doTest("insert comment", $success)) {
$comment = (new PlaylistEntry())->setComment(self::TEST_COMMENT);
$success2 = $papi->insertTrackEntry($pid, $comment, $status);
$this->showSuccess($success2);
} else
$success2 = false;

if($this->doTest("insert spin", $success)) {
$spin = new PlaylistEntry([
'artist'=>'TEST, Artist',
'album'=>'TEST Album',
'track'=>self::TEST_TRACK,
'label'=>'TEST Label'
]);
$success3 = $papi->insertTrackEntry($pid, $spin, $status);
$this->showSuccess($success3);
} else
$success3 = false;

if($this->doTest("move track", $success2 && $success3)) {
$success4 = $papi->moveTrack($pid, $spin->getId(), $comment->getId());
$this->showSuccess($success4);
} else
$success4 = false;

if($this->doTest("view playlist", $success4)) {
$stream = popen(__DIR__."/../".
"zk main action=viewListById subaction= playlist=$pid", "r");
$page = stream_get_contents($stream);
pclose($stream);

// scrape the page looking for the comment and spin we inserted.
// both should be present, and the comment should follow the spin
$commentPos = strpos($page, self::TEST_COMMENT);
$trackPos = strpos($page, self::TEST_TRACK);
$success5 = $commentPos !== false && $trackPos !== false &&
$commentPos > $trackPos;
$this->showSuccess($success5);
}

if($this->doTest("validate search", $success3)) {
$stream = popen(__DIR__."/../".
"zk api method=searchRq type= offset= size=5 key=".
explode(' ', self::TEST_TRACK)[1], "r");
$page = stream_get_contents($stream);
pclose($stream);

// parse the json looking for the spin
$success6 = false;
$json = json_decode($page);
foreach($json->data as $data) {
if($data->type == "playlists") {
foreach($data->data as $playlist) {
if($playlist->track == self::TEST_TRACK) {
$success6 = true;
break 2;
}
}
break;
}
}
$this->showSuccess($success6);
}

if($this->doTest("delete playlist", $success)) {
$papi->deletePlaylist($pid);
$this->showSuccess(true);
}

if($this->doTest("purge playlists", $success)) {
$success = $papi->purgeDeletedPlaylists(0);
$this->showSuccess($success);
}
}

public function validateCategories() {
if($this->doTest("validate categories", isset($this->testUser))) {
$cats = Engine::api(IChart::class)->getCategories();
$success = sizeof($cats) == 16;
$this->showSuccess($success);
}
}

public function validateDeleteUser() {
if($this->doTest("delete user", isset($this->testUser))) {
// invalidate session
$this->session->invalidate();

$success = Engine::api(IUser::class)->deleteUser($this->testUser);
$this->showSuccess($success);
}
}
}
2 changes: 1 addition & 1 deletion engine/IPlaylist.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ function getLastPlays($tag, $count=0);
function getRecentPlays($airname, $count);
function deletePlaylist($playlist);
function restorePlaylist($playlist);
function purgeDeletedPlaylists();
function purgeDeletedPlaylists($days=30);
function getDeletedPlaylistCount($user);
function getListsSelNormal($user);
function getListsSelDeleted($user);
Expand Down
1 change: 1 addition & 0 deletions engine/IUser.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,5 @@ function createNewAccount($fullname, $account);
function validatePassword($user, $password, $updateTimestamp, &$groups=0);
function updateUser($user, $password, $realname="XXZZ", $groups="XXZZ", $expiration="XXZZ");
function insertUser($user, $password, $realname, $groups, $expiration);
function deleteUser($user);
}
5 changes: 3 additions & 2 deletions engine/impl/Playlist.php
Original file line number Diff line number Diff line change
Expand Up @@ -820,13 +820,14 @@ public function restorePlaylist($playlist) {
$stmt->execute();
}

public function purgeDeletedPlaylists() {
public function purgeDeletedPlaylists($days=30) {
$query = "DELETE FROM tracks, lists, lists_del USING lists_del " .
"INNER JOIN lists " .
"LEFT OUTER JOIN tracks ON lists_del.listid = tracks.list " .
"WHERE lists_del.listid = lists.id ".
"AND ADDDATE(deleted, 30) < NOW()";
"AND ADDDATE(deleted, ?) < NOW()";
$stmt = $this->prepare($query);
$stmt->bindValue(1, $days);
return $stmt->execute();
}

Expand Down
25 changes: 25 additions & 0 deletions engine/impl/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -253,4 +253,29 @@ public function insertUser($user, $password, $realname, $groups, $expiration) {
$stmt->execute();
return ($stmt->rowCount() > 0);
}

public function deleteUser($user) {
// validate this user has no playlists nor reviews
$query = "SELECT COUNT(*) c FROM lists WHERE dj = ?";
$stmt = $this->prepare($query);
$stmt->bindValue(1, $user);
$result = $stmt->executeAndFetch();
if($result['c'])
return false;

$query = "SELECT COUNT(*) c FROM reviews WHERE user = ?";
$stmt = $this->prepare($query);
$stmt->bindValue(1, $user);
$result = $stmt->executeAndFetch();
if($result['c'])
return false;

// remove any airnames
Engine::api(IDJ::class)->getAirnames($user);

$query = "DELETE FROM users WHERE name = ?";
$stmt = $this->prepare($query);
$stmt->bindValue(1, $user);
return $stmt->execute();
}
}
4 changes: 2 additions & 2 deletions ui/UIController.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,8 @@ public function processRequest() {

$this->preProcessRequest();

$isJson = substr($_SERVER["HTTP_ACCEPT"], 0, 16) === 'application/json';
$isJson = isset($_SERVER["HTTP_ACCEPT"]) &&
substr($_SERVER["HTTP_ACCEPT"], 0, 16) === 'application/json';
if ($isJson) {
$action = $_REQUEST["action"];
$subaction = $_REQUEST["subaction"];
Expand Down Expand Up @@ -174,7 +175,6 @@ protected function preProcessRequest() {
}

protected function emitResponseHeader() {
$userAgent = $_SERVER["HTTP_USER_AGENT"];
$banner = Engine::param('application');
$station = Engine::param('station');
$station_full = Engine::param('station_full');
Expand Down

0 comments on commit b5a8fb5

Please sign in to comment.