diff options
Diffstat (limited to 'application/test')
24 files changed, 2344 insertions, 0 deletions
diff --git a/application/test/Test.php b/application/test/Test.php new file mode 100644 index 000000000..b8052fbba --- /dev/null +++ b/application/test/Test.php @@ -0,0 +1,186 @@ +<?php +/* + * Copyright 2015 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +namespace test; + +require_once APPPATH."/third_party/test-more-php/Test-More-OO.php"; + +class TestMore extends \TestMore { + private $TestNamePrefix = ""; + + public function setTestNamePrefix($prefix) { + $this->TestNamePrefix = $prefix; + } + + public function ok ($Result = NULL, $TestName = NULL) { + return parent::ok($Result, $this->TestNamePrefix.$TestName); + } +} + +abstract class Test { + protected $t; + protected $server_url = ""; + private $testid = ""; + private $server_proc = null; + + public function __construct() + { + $this->t = new TestMore(); + $this->t->plan("no_plan"); + } + + public function __destruct() + { + if ($this->server_proc) { + proc_terminate($this->server_proc); + } + } + + public function startServer($port) + { + $url = "http://127.0.0.1:$port/index.php"; + + $pipes = []; + $descriptorspec = [ + 0 => ['file', '/dev/null', 'r'], + 1 => STDOUT, + 2 => STDOUT, + ]; + + $this->server_proc = proc_open("php -S 127.0.0.1:$port", $descriptorspec, $pipes); + + $this->wait_for_server($url); + $this->server_url = $url; + } + + private function wait_for_server($url) + { + while (!$this->url_is_reachable($url)) { + echo "Waiting for server at $url to start...\n"; + usleep(10000); + } + } + + private function url_is_reachable($url) + { + $handle = curl_init($url); + curl_setopt($handle, CURLOPT_RETURNTRANSFER, TRUE); + curl_exec($handle); + $status = curl_getinfo($handle, CURLINFO_HTTP_CODE); + curl_close($handle); + + if ($status == 200) { + return true; + } + + return false; + } + + public function setTestID($testid) + { + $this->testid = $testid; + } + + // Method: POST, PUT, GET etc + // Data: array("param" => "value") ==> index.php?param=value + // Source: http://stackoverflow.com/a/9802854/953022 + protected function CallAPI($method, $url, $data = false, $return_json = false) + { + $result = $this->SendHTTPRequest($method, $url, $data); + + if ($return_json) { + return $result; + } + + $json = json_decode($result, true); + if ($json === NULL) { + $this->t->fail("json decode"); + $this->diagReply($result); + } + + return $json; + } + + protected function SendHTTPRequest($method, $url, $data = false) + { + $curl = curl_init(); + + switch ($method) { + case "POST": + curl_setopt($curl, CURLOPT_POST, 1); + + if ($data) + curl_setopt($curl, CURLOPT_POSTFIELDS, $data); + break; + case "PUT": + curl_setopt($curl, CURLOPT_PUT, 1); + break; + default: + if ($data) + $url = sprintf("%s?%s", $url, http_build_query($data)); + } + + curl_setopt($curl, CURLOPT_URL, $url); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_HTTPHEADER, array( + "Accept: application/json", + "X-Testsuite-Testname: API request from ".$this->testid, + "Expect: ", + )); + + $result = curl_exec($curl); + + curl_close($curl); + return $result; + } + + protected function excpectStatus($testname, $reply, $status) + { + if (!isset($reply["status"]) || $reply["status"] != $status) { + $this->t->fail($testname); + $this->diagReply($reply); + } else { + $this->t->pass($testname); + } + return $reply; + } + + protected function expectSuccess($testname, $reply) + { + return $this->excpectStatus($testname, $reply, "success"); + } + + protected function expectError($testname, $reply) + { + return $this->excpectStatus($testname, $reply, "error"); + } + + protected function diagReply($reply) + { + $this->t->diag("Request got unexpected response:"); + $this->t->diag(var_export($reply, true)); + } + + public function init() + { + } + + public function cleanup() + { + } + + public function done_testing() + { + $this->t->done_testing(); + } + + public function setTestNamePrefix($prefix) { + $this->t->setTestNamePrefix($prefix); + } +} diff --git a/application/test/tests/api_v2/common.php b/application/test/tests/api_v2/common.php new file mode 100644 index 000000000..103e156a8 --- /dev/null +++ b/application/test/tests/api_v2/common.php @@ -0,0 +1,60 @@ +<?php +/* + * Copyright 2015 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +namespace test\tests\api_v2; + +class common extends \test\Test { + + protected $userCounter = null; + + public function __construct() + { + parent::__construct(); + + $CI =& get_instance(); + $CI->load->model("muser"); + $CI->load->model("mfile"); + } + + protected function uploadFile($apikey, $file) + { + $ret = $this->CallAPI("POST", "$this->server_url/api/v2.0.0/file/upload", array( + "apikey" => $apikey, + "file[1]" => curl_file_create($file), + )); + $this->expectSuccess("upload file", $ret); + return $ret; + } + + protected function createUser($counter) + { + $CI =& get_instance(); + $CI->muser->add_user("apiv2testuser$counter", "testpass$counter", + "testuser$counter@testsuite.local", NULL); + return $CI->db->insert_id(); + } + + protected function createApikey($userid, $access_level = "apikey") + { + return \service\user::create_apikey($userid, "", $access_level); + } + + protected function createUserAndApikey($access_level = "apikey") + { + assert($this->userCounter !== null); + $this->userCounter++; + $userid = $this->createUser($this->userCounter); + return $this->createApikey($userid, $access_level); + } + + protected function callEndpoint($verb, $endpoint, $data, $return_json = false) + { + return $this->CallAPI($verb, "$this->server_url/api/v2.0.0/$endpoint", $data, $return_json); + } +} diff --git a/application/test/tests/api_v2/test_api_permissions.php b/application/test/tests/api_v2/test_api_permissions.php new file mode 100644 index 000000000..6df612911 --- /dev/null +++ b/application/test/tests/api_v2/test_api_permissions.php @@ -0,0 +1,108 @@ +<?php +/* + * Copyright 2015-2016 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +namespace test\tests\api_v2; + +class test_api_permissions extends common { + + public function __construct() + { + parent::__construct(); + $this->startServer(23200); + $this->userCounter = 100; + } + + public function test_callPrivateEndpointsWithoutApikey() + { + $endpoints = array( + "file/upload", + "file/history", + "file/delete", + "file/create_multipaste", + "user/apikeys", + "user/create_apikey", + "user/delete_apikey", + ); + foreach ($endpoints as $endpoint) { + $ret = $this->CallEndpoint("POST", $endpoint, array( + )); + $this->expectError("call $endpoint without apikey", $ret); + $this->t->is_deeply(array( + 'status' => 'error', + 'error_id' => 'api/not-authenticated', + 'message' => 'Not authenticated. FileBin requires you to have an account, please go to the homepage at http://127.0.0.1:23200/ for more information.', + ), $ret, "expected error"); + } + } + + public function test_callPrivateEndpointsWithUnsupportedAuthentication() + { + $endpoints = array( + "file/upload", + "file/history", + "file/delete", + "file/create_multipaste", + "user/apikeys", + // create_apikey is the only one that supports username/pw + //"user/create_apikey", + "user/delete_apikey", + ); + foreach ($endpoints as $endpoint) { + $ret = $this->CallEndpoint("POST", $endpoint, array( + "username" => "apiv2testuser1", + "password" => "testpass1", + )); + $this->expectError("call $endpoint without apikey", $ret); + $this->t->is_deeply(array( + 'status' => 'error', + 'error_id' => 'api/not-authenticated', + 'message' => 'Not authenticated. FileBin requires you to have an account, please go to the homepage at http://127.0.0.1:23200/ for more information.', + ), $ret, "expected error"); + } + } + + public function test_callEndpointsWithoutEnoughPermissions() + { + $testconfig = array( + array( + "have_level" => "basic", + "wanted_level" => "apikey", + "apikey" => $this->createUserAndApikey('basic'), + "endpoints" => array( + "file/delete", + "file/history", + ), + ), + array( + "have_level" => "apikey", + "wanted_level" => "full", + "apikey" => $this->createUserAndApikey(), + "endpoints" => array( + "user/apikeys", + "user/create_apikey", + "user/delete_apikey", + ), + ), + ); + foreach ($testconfig as $test) { + foreach ($test['endpoints'] as $endpoint) { + $ret = $this->CallEndpoint("POST", $endpoint, array( + "apikey" => $test['apikey'], + )); + $this->expectError("call $endpoint without enough permissions", $ret); + $this->t->is_deeply(array( + 'status' => "error", + 'error_id' => "api/insufficient-permissions", + 'message' => "Access denied: Access level too low. Required: ${test['wanted_level']}; Have: ${test['have_level']}", + ), $ret, "expected permission error"); + } + } + } + +} diff --git a/application/test/tests/api_v2/test_create_apikey.php b/application/test/tests/api_v2/test_create_apikey.php new file mode 100644 index 000000000..203eb5531 --- /dev/null +++ b/application/test/tests/api_v2/test_create_apikey.php @@ -0,0 +1,66 @@ +<?php +/* + * Copyright 2016 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +namespace test\tests\api_v2; + +class test_create_apikey extends common { + + public function __construct() + { + parent::__construct(); + $this->startServer(23202); + $this->userCounter = 2100; + } + + public function test_create_apikey_createNewKey() + { + $this->createUser(1); + $ret = $this->CallEndpoint("POST", "user/create_apikey", array( + "username" => "apiv2testuser1", + "password" => "testpass1", + "access_level" => "apikey", + "comment" => "main api key", + )); + $this->expectSuccess("create-apikey", $ret); + + $this->t->isnt($ret["data"]["new_key"], "", "apikey not empty"); + } + + public function test_authentication_invalidPassword() + { + $userid = $this->createUser(3); + $ret = $this->CallEndpoint("POST", "user/create_apikey", array( + "username" => "apiv2testuser3", + "password" => "wrongpass", + )); + $this->expectError("invalid password", $ret); + + $this->t->is_deeply(array ( + 'status' => 'error', + 'error_id' => 'user/login-failed', + 'message' => 'Login failed', + ), $ret, "expected error"); + } + + public function test_authentication_invalidUser() + { + $userid = $this->createUser(4); + $ret = $this->CallEndpoint("POST", "user/create_apikey", array( + "username" => "apiv2testuserinvalid", + "password" => "testpass4", + )); + $this->expectError("invalid username", $ret); + + $this->t->is_deeply(array ( + 'status' => 'error', + 'error_id' => 'user/login-failed', + 'message' => 'Login failed', + ), $ret, "expected error"); + } +} diff --git a/application/test/tests/api_v2/test_file_create_multipaste.php b/application/test/tests/api_v2/test_file_create_multipaste.php new file mode 100644 index 000000000..2b6e9d8de --- /dev/null +++ b/application/test/tests/api_v2/test_file_create_multipaste.php @@ -0,0 +1,148 @@ +<?php +/* + * Copyright 2016 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +namespace test\tests\api_v2; + +class test_file_create_multipaste extends common { + + public function __construct() + { + parent::__construct(); + $this->startServer(23204); + $this->userCounter = 4100; + } + + public function test_create_multipaste_canCreate() + { + $apikey = $this->createUserAndApikey("basic"); + $ret = $this->uploadFile($apikey, "data/tests/small-file"); + $id = $ret["data"]["ids"][0]; + + $ret = $this->uploadFile($apikey, "data/tests/small-file"); + $id2 = $ret["data"]["ids"][0]; + + $ret = $this->CallEndpoint("POST", "file/create_multipaste", array( + "apikey" => $apikey, + "ids[1]" => $id, + "ids[2]" => $id2, + )); + $this->expectSuccess("create multipaste", $ret); + + $this->t->isnt($ret["data"]["url_id"], "", "got a multipaste ID"); + $this->t->isnt($ret["data"]["url"], "", "got a multipaste URL"); + } + + public function test_create_multipaste_errorOnWrongID() + { + $apikey = $this->createUserAndApikey("basic"); + $ret = $this->uploadFile($apikey, "data/tests/small-file"); + $id = $ret["data"]["ids"][0]; + + $id2 = $id."invalid"; + $ret = $this->CallEndpoint("POST", "file/create_multipaste", array( + "apikey" => $apikey, + "ids[1]" => $id, + "ids[2]" => $id2, + )); + $this->expectError("create multipaste with wrong ID", $ret); + + $this->t->is_deeply(array( + 'status' => 'error', + 'error_id' => 'file/create_multipaste/verify-failed', + 'message' => 'Failed to verify ID(s)', + 'data' => + array ( + $id2 => + array ( + 'id' => $id2, + 'reason' => 'doesn\'t exist', + ), + ), + ), $ret, "expected error response"); + } + + public function test_create_multipaste_errorOnWrongOwner() + { + $apikey = $this->createUserAndApikey("basic"); + $apikey2 = $this->createUserAndApikey("basic"); + $ret = $this->uploadFile($apikey, "data/tests/small-file"); + $id = $ret["data"]["ids"][0]; + + $ret = $this->CallEndpoint("POST", "file/create_multipaste", array( + "apikey" => $apikey2, + "ids[1]" => $id, + )); + $this->expectError("create multipaste with wrong owner", $ret); + + $this->t->is_deeply(array( + 'status' => 'error', + 'error_id' => 'file/create_multipaste/verify-failed', + 'message' => 'Failed to verify ID(s)', + 'data' => + array ( + $id => + array ( + 'id' => $id, + 'reason' => 'not owned by you', + ), + ), + ), $ret, "expected error response"); + } + + public function test_delete_canDeleteMultipaste() + { + $apikey = $this->createUserAndApikey(); + $ret = $this->uploadFile($apikey, "data/tests/small-file"); + $id = $ret["data"]["ids"][0]; + $ret = $this->CallEndpoint("POST", "file/create_multipaste", array( + "apikey" => $apikey, + "ids[1]" => $id, + )); + $this->expectSuccess("create multipaste", $ret); + + $mid = $ret['data']['url_id']; + $ret = $this->CallEndpoint("POST", "file/delete", array( + "apikey" => $apikey, + "ids[1]" => $mid, + )); + $this->expectSuccess("delete uploaded file", $ret); + + $this->t->ok(empty($ret["data"]["errors"]), "no errors"); + $this->t->is_deeply(array( + $mid => array( + "id" => $mid + ) + ), $ret["data"]["deleted"], "deleted wanted ID"); + $this->t->is($ret["data"]["total_count"], 1, "total_count correct"); + $this->t->is($ret["data"]["deleted_count"], 1, "deleted_count correct"); + } + + public function test_create_multipaste_minidlength() + { + $apikey = $this->createUserAndApikey("basic"); + $ret = $this->uploadFile($apikey, "data/tests/small-file"); + $id = $ret["data"]["ids"][0]; + + $ret = $this->uploadFile($apikey, "data/tests/small-file"); + $id2 = $ret["data"]["ids"][0]; + + $ret = $this->CallEndpoint("POST", "file/create_multipaste", array( + "apikey" => $apikey, + "ids[1]" => $id, + "ids[2]" => $id2, + "minimum-id-length" => 42, + )); + $this->expectSuccess("create multipaste", $ret); + + $this->t->isnt($ret["data"]["url_id"], "", "got a multipaste ID"); + $this->t->isnt($ret["data"]["url"], "", "got a multipaste URL"); + + $this->t->ok(strlen($ret["data"]["url_id"]) >= 42, "minimum url length upheld"); + } +} diff --git a/application/test/tests/api_v2/test_file_delete.php b/application/test/tests/api_v2/test_file_delete.php new file mode 100644 index 000000000..d9ffc5b2c --- /dev/null +++ b/application/test/tests/api_v2/test_file_delete.php @@ -0,0 +1,82 @@ +<?php +/* + * Copyright 2016 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +namespace test\tests\api_v2; + +class test_file_delete extends common { + + public function __construct() + { + parent::__construct(); + $this->startServer(23203); + $this->userCounter = 3100; + } + + public function test_delete_canDeleteUploaded() + { + $apikey = $this->createUserAndApikey(); + $ret = $this->uploadFile($apikey, "data/tests/small-file"); + $id = $ret["data"]["ids"][0]; + + $ret = $this->CallEndpoint("POST", "file/delete", array( + "apikey" => $apikey, + "ids[1]" => $id, + )); + $this->expectSuccess("delete uploaded file", $ret); + + $this->t->ok(empty($ret["data"]["errors"]), "no errors"); + $this->t->is_deeply(array( + $id => array( + "id" => $id + ) + ), $ret["data"]["deleted"], "deleted wanted ID"); + $this->t->is($ret["data"]["total_count"], 1, "total_count correct"); + $this->t->is($ret["data"]["deleted_count"], 1, "deleted_count correct"); + } + + public function test_delete_errorIfNotOwner() + { + $apikey = $this->createUserAndApikey(); + $apikey2 = $this->createUserAndApikey(); + $ret = $this->uploadFile($apikey, "data/tests/small-file"); + $id = $ret["data"]["ids"][0]; + + $ret = $this->CallEndpoint("POST", "file/delete", array( + "apikey" => $apikey2, + "ids[1]" => $id, + )); + $this->expectSuccess("delete file of someone else", $ret); + + $this->t->ok(empty($ret["data"]["deleted"]), "not deleted"); + $this->t->is_deeply(array( + $id => array( + "id" => $id, + "reason" => "wrong owner" + ) + ), $ret["data"]["errors"], "error wanted ID"); + $this->t->is($ret["data"]["total_count"], 1, "total_count correct"); + $this->t->is($ret["data"]["deleted_count"], 0, "deleted_count correct"); + } + + public function test_delete_empty_json_structure() + { + $apikey = $this->createUserAndApikey(); + $ret = $this->uploadFile($apikey, "data/tests/small-file"); + $id = $ret["data"]["ids"][0]; + + $ret = $this->CallEndpoint("POST", "file/delete", array( + "apikey" => $apikey, + "ids[1]" => $id, + ), true); + + $this->t->is($ret, '{"status":"success","data":{"errors":{},"deleted":{"'.$id.'":{"id":"'.$id.'"}},"total_count":1,"deleted_count":1}}', "empty lists should be json objects, not arrays"); + } + + +} diff --git a/application/test/tests/api_v2/test_file_upload.php b/application/test/tests/api_v2/test_file_upload.php new file mode 100644 index 000000000..07769774f --- /dev/null +++ b/application/test/tests/api_v2/test_file_upload.php @@ -0,0 +1,112 @@ +<?php +/* + * Copyright 2016 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +namespace test\tests\api_v2; + +class test_file_upload extends common { + + public function __construct() + { + parent::__construct(); + $this->startServer(23205); + $this->userCounter = 5100; + } + + public function test_upload_uploadFile() + { + $apikey = $this->createUserAndApikey(); + $ret = $this->CallEndpoint("POST", "file/upload", array( + "apikey" => $apikey, + "file[1]" => curl_file_create("data/tests/small-file"), + )); + $this->expectSuccess("upload file", $ret); + + $this->t->ok(!empty($ret["data"]["ids"]), "got IDs"); + $this->t->ok(!empty($ret["data"]["urls"]), "got URLs"); + } + + public function test_upload_uploadFileSameMD5() + { + $apikey = $this->createUserAndApikey(); + $ret = $this->CallEndpoint("POST", "file/upload", array( + "apikey" => $apikey, + "file[1]" => curl_file_create("data/tests/message1.bin"), + "file[2]" => curl_file_create("data/tests/message2.bin"), + )); + $this->expectSuccess("upload file", $ret); + + $this->t->ok(!empty($ret["data"]["ids"]), "got IDs"); + $this->t->ok(!empty($ret["data"]["urls"]), "got URLs"); + + foreach ($ret["data"]["urls"] as $url) { + # remove tailing / + $url = substr($url, 0, strlen($url) - 1); + $data[] = $this->SendHTTPRequest("GET", $url, ''); + } + $this->t->ok($data[0] !== $data[1], 'Returned file contents should differ'); + $this->t->is($data[0], file_get_contents("data/tests/message1.bin"), "Returned correct data for file 1"); + $this->t->is($data[1], file_get_contents("data/tests/message2.bin"), "Returned correct data for file 2"); + } + + public function test_upload_uploadNothing() + { + $apikey = $this->createUserAndApikey(); + $ret = $this->CallEndpoint("POST", "file/upload", array( + "apikey" => $apikey, + )); + $this->expectError("upload no file", $ret); + $this->t->is_deeply(array( + 'status' => 'error', + 'error_id' => 'file/no-file', + 'message' => 'No file was uploaded or unknown error occurred.', + ), $ret, "expected reply"); + } + + public function test_upload_minidlength() + { + $apikey = $this->createUserAndApikey(); + $ret = $this->CallEndpoint("POST", "file/upload", array( + "apikey" => $apikey, + "file[1]" => curl_file_create("data/tests/small-file"), + "minimum-id-length" => 42, + )); + $this->expectSuccess("upload file", $ret); + + foreach ($ret["data"]["urls"] as $url) { + $matches = array(); + preg_match('/\/([^\/]+)\/$/', $url, $matches); + $this->t->ok(strlen($matches[1]) >= 42, "minimum url length upheld"); + } + } + + public function test_upload_bad_minidlength() + { + $apikey = $this->createUserAndApikey(); + + $combinations = [ + "non-numberic minimum-id-length" => "nonumber", + "negative minimum-id-length (-42)" => -42, + "minimum-id-length=0" => 0, + "minimum-id-length=1" => 1, + ]; + foreach ($combinations as $msg => $input) { + $ret = $this->CallEndpoint("POST", "file/upload", array( + "apikey" => $apikey, + "file[1]" => curl_file_create("data/tests/small-file"), + "minimum-id-length" => $input, + )); + $this->expectError("upload file with bad minimum-id-length. Test value: $msg", $ret); + $this->t->is_deeply(array( + 'status' => 'error', + 'error_id' => 'file/bad-minimum-id-length', + 'message' => "Passed parameter 'minimum-id-length' is not a valid integer or too small (min value: 2)", + ), $ret, "expected reply"); + } + } +} diff --git a/application/test/tests/api_v2/test_history.php b/application/test/tests/api_v2/test_history.php new file mode 100644 index 000000000..f09aab9bb --- /dev/null +++ b/application/test/tests/api_v2/test_history.php @@ -0,0 +1,137 @@ +<?php +/* + * Copyright 2016 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +namespace test\tests\api_v2; + +class test_history extends common { + + public function __construct() + { + parent::__construct(); + $this->startServer(23201); + $this->userCounter = 1100; + } + + public function test_history_empty() + { + $apikey = $this->createUserAndApikey(); + $ret = $this->CallEndpoint("POST", "file/history", array( + "apikey" => $apikey, + )); + $this->expectSuccess("get history", $ret); + + $this->t->ok(empty($ret["data"]["items"]), "items key exists and empty"); + $this->t->ok(empty($ret["data"]["multipaste_items"]), "multipaste_items key exists and empty"); + $this->t->is($ret["data"]["total_size"], "0", "total_size = 0 since no uploads"); + } + + public function test_history_empty_json_structure() + { + $apikey = $this->createUserAndApikey(); + $ret = $this->CallEndpoint("POST", "file/history", array( + "apikey" => $apikey, + ), true); + + $this->t->is($ret, '{"status":"success","data":{"items":{},"multipaste_items":{},"total_size":"0"}}', "empty lists should be json objects, not arrays"); + } + + public function test_history_notEmptyAfterUploadSameMD5() + { + $apikey = $this->createUserAndApikey(); + $this->CallEndpoint("POST", "file/upload", array( + "apikey" => $apikey, + "file[1]" => curl_file_create("data/tests/message1.bin"), + "file[2]" => curl_file_create("data/tests/message2.bin"), + )); + $expected_filesize = filesize("data/tests/message1.bin") + filesize("data/tests/message2.bin"); + + $ret = $this->CallEndpoint("POST", "file/history", array( + "apikey" => $apikey, + )); + $this->expectSuccess("history not empty after upload", $ret); + + $this->t->ok(!empty($ret["data"]["items"]), "history not empty after upload (items)"); + $this->t->ok(empty($ret["data"]["multipaste_items"]), "didn't upload multipaste"); + $this->t->is($ret["data"]["total_size"], "$expected_filesize", "total_size == uploaded files"); + } + + public function test_history_notEmptyAfterMultipaste() + { + $apikey = $this->createUserAndApikey(); + $uploadid = $this->uploadFile($apikey, "data/tests/small-file")['data']['ids'][0]; + $multipasteid = $this->CallEndpoint("POST", "file/create_multipaste", array( + "apikey" => $apikey, + 'ids[1]' => $uploadid, + ))['data']['url_id']; + + $ret = $this->CallEndpoint("POST", "file/history", array( + "apikey" => $apikey, + )); + $this->expectSuccess("history not empty after multipaste", $ret); + + $this->t->ok(!empty($ret["data"]["items"]), "history not empty after multipaste (items)"); + $this->t->is($ret['data']["multipaste_items"][$multipasteid]['items'][$uploadid]['id'], $uploadid, "multipaste contains correct id"); + $this->t->is_deeply(array( + 'url_id', 'date', 'items' + ), array_keys($ret['data']["multipaste_items"][$multipasteid]), "multipaste info only lists correct keys"); + $this->t->is_deeply(array('id'), array_keys($ret['data']["multipaste_items"][$multipasteid]['items'][$uploadid]), "multipaste item info only lists correct keys"); + } + + public function test_history_notEmptyAfterUpload() + { + $apikey = $this->createUserAndApikey(); + $uploadid = $this->uploadFile($apikey, "data/tests/small-file")['data']['ids'][0]; + $uploadid_image = $this->uploadFile($apikey, "data/tests/black_white.png")['data']['ids'][0]; + $expected_size = filesize("data/tests/small-file") + filesize("data/tests/black_white.png"); + + $ret = $this->CallEndpoint("POST", "file/history", array( + "apikey" => $apikey, + )); + $this->expectSuccess("history not empty after upload", $ret); + + $this->t->ok(!empty($ret["data"]["items"]), "history not empty after upload (items)"); + $this->t->is_deeply(array( + 'id', 'filename', 'mimetype', 'date', 'hash', 'filesize' + ), array_keys($ret['data']["items"][$uploadid]), "item info only lists correct keys"); + $this->t->is_deeply(array( + 'id', 'filename', 'mimetype', 'date', 'hash', 'filesize', 'thumbnail' + ), array_keys($ret['data']["items"][$uploadid_image]), "item info for image lists thumbnail too"); + $this->t->ok(empty($ret["data"]["multipaste_items"]), "didn't upload multipaste"); + $this->t->is($ret["data"]["total_size"], "$expected_size", "total_size == uploaded files"); + } + + public function test_history_notSharedBetweenUsers() + { + $apikey = $this->createUserAndApikey(); + $apikey2 = $this->createUserAndApikey(); + $this->uploadFile($apikey, "data/tests/small-file"); + + $ret = $this->CallEndpoint("POST", "file/history", array( + "apikey" => $apikey2, + )); + $this->expectSuccess("get history", $ret); + + $this->t->ok(empty($ret["data"]["items"]), "items key exists and empty"); + $this->t->ok(empty($ret["data"]["multipaste_items"]), "multipaste_items key exists and empty"); + $this->t->is($ret["data"]["total_size"], "0", "total_size = 0 since no uploads"); + } + + public function test_history_specialVarsNotExpanded() + { + $apikey = $this->createUserAndApikey(); + $uploadid = $this->uploadFile($apikey, "data/tests/{elapsed_time}.txt")['data']['ids'][0]; + + $ret = $this->CallEndpoint("POST", "file/history", array( + "apikey" => $apikey, + )); + $this->expectSuccess("get history", $ret); + + $this->t->is($ret["data"]["items"][$uploadid]['filename'], '{elapsed_time}.txt', "{elapsed_time} is not expanded in history reply"); + } +} diff --git a/application/test/tests/api_v2/test_misc.php b/application/test/tests/api_v2/test_misc.php new file mode 100644 index 000000000..e7c249054 --- /dev/null +++ b/application/test/tests/api_v2/test_misc.php @@ -0,0 +1,48 @@ +<?php +/* + * Copyright 2016 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +namespace test\tests\api_v2; + +class test_misc extends common { + + public function __construct() + { + parent::__construct(); + $this->startServer(23207); + $this->userCounter = 7100; + } + + public function test_apikeys_getApikey() + { + $userid = $this->createUser(2); + $apikey = $this->createApikey($userid); + $apikey_full = $this->createApikey($userid, "full"); + $ret = $this->CallEndpoint("POST", "user/apikeys", array( + "apikey" => $apikey_full, + )); + $this->expectSuccess("get apikeys", $ret); + + $this->t->is($ret["data"]["apikeys"][$apikey]["key"], $apikey, "expected key 1"); + $this->t->is($ret["data"]["apikeys"][$apikey]["access_level"], "apikey", "expected key 1 acces_level"); + $this->t->is($ret["data"]["apikeys"][$apikey]["comment"], "", "expected key 1 comment"); + $this->t->ok(is_int($ret["data"]["apikeys"][$apikey]["created"]) , "expected key 1 creation time is int"); + } + + public function test_get_config() + { + $ret = $this->CallEndpoint("GET", "file/get_config", array( + )); + $this->expectSuccess("get_config", $ret); + + $this->t->like($ret["data"]["upload_max_size"], '/[0-9]+/', "upload_max_size is int"); + $this->t->like($ret["data"]["max_files_per_request"], '/[0-9]+/', "max_files_per_request is int"); + } + + +} diff --git a/application/test/tests/api_v2/test_user_delete_apikey.php b/application/test/tests/api_v2/test_user_delete_apikey.php new file mode 100644 index 000000000..062b0d6c1 --- /dev/null +++ b/application/test/tests/api_v2/test_user_delete_apikey.php @@ -0,0 +1,49 @@ +<?php +/* + * Copyright 2016 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +namespace test\tests\api_v2; + +class test_user_delete_apikey extends common { + + public function __construct() + { + parent::__construct(); + $this->startServer(23206); + $this->userCounter = 6100; + } + + public function test_delete_apikey_deleteOwnKey() + { + $apikey = $this->createUserAndApikey("full"); + $ret = $this->CallEndpoint("POST", "user/delete_apikey", array( + "apikey" => $apikey, + "delete_key" => $apikey, + )); + $this->expectSuccess("delete apikey", $ret); + + $this->t->is($ret["data"]["deleted_keys"][$apikey]["key"], $apikey, "expected key"); + } + + public function test_delete_apikey_errorDeleteOtherUserKey() + { + $apikey = $this->createUserAndApikey("full"); + $apikey2 = $this->createUserAndApikey("full"); + $ret = $this->CallEndpoint("POST", "user/delete_apikey", array( + "apikey" => $apikey, + "delete_key" => $apikey2, + )); + $this->expectError("delete apikey of other user", $ret); + $this->t->is_deeply(array( + 'status' => 'error', + 'error_id' => 'user/delete_apikey/failed', + 'message' => 'Apikey deletion failed. Possibly wrong owner.', + ), $ret, "expected error"); + } + +} diff --git a/application/test/tests/test_database_schema.php b/application/test/tests/test_database_schema.php new file mode 100644 index 000000000..02f188e1f --- /dev/null +++ b/application/test/tests/test_database_schema.php @@ -0,0 +1,38 @@ +<?php +/* + * Copyright 2017 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +namespace test\tests; + +class test_database_schema extends \test\Test { + + public function __construct() + { + parent::__construct(); + } + + public function test_file_storage_bigint() { + $filesize = pow(2, 35) + 1; + + $CI =& get_instance(); + $CI->db->insert("file_storage", array( + "filesize" => $filesize, + "mimetype" => "text/plain", + "hash" => md5("test"), + "date" => time(), + )); + $id = $CI->db->insert_id(); + $db_value = $CI->db->select('filesize') + ->from('file_storage') + ->where('id', $id) + ->get()->result_array()[0]["filesize"]; + $this->t->is(intval($db_value), $filesize, "Large filesize is stored correctly in db"); + } + + +} diff --git a/application/test/tests/test_filebin_helper.php b/application/test/tests/test_filebin_helper.php new file mode 100644 index 000000000..a46d4bc3c --- /dev/null +++ b/application/test/tests/test_filebin_helper.php @@ -0,0 +1,108 @@ +<?php +/* + * Copyright 2016 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +namespace test\tests; + +class test_filebin_helper extends \test\Test { + + public function __construct() + { + parent::__construct(); + } + + public function init() + { + } + + public function cleanup() + { + } + + public function test_expiration_duration() + { + $this->t->is(expiration_duration(60*60*24*2), "2 days", "2 days"); + $this->t->is(expiration_duration(60*60*24), "1 day", "1 day"); + $this->t->is(expiration_duration(60*60*2), "2 hours", "2 hours"); + $this->t->is(expiration_duration(60*60), "1 hour", "1 hour"); + $this->t->is(expiration_duration(60*2), "2 minutes", "2 minutes"); + $this->t->is(expiration_duration(60), "1 minute", "1 minute"); + $this->t->is(expiration_duration(59), "59 seconds", "59 seconds"); + $this->t->is(expiration_duration(1), "1 second", "1 second"); + + $this->t->is(expiration_duration(60*60*24 + 60*60 + 60), "1 day, 1 hour, 1 minute", "1 day, 1 hour, 1 minute"); + $this->t->is(expiration_duration(60*60*24 + 60*60 + 120), "1 day, 1 hour, 2 minutes", "1 day, 1 hour, 2 minutes"); + $this->t->is(expiration_duration(60*60*24 + 60*60*2 + 60), "1 day, 2 hours, 1 minute", "1 day, 2 hours, 1 minute"); + $this->t->is(expiration_duration(60*60*24 + 60*60*2 + 120), "1 day, 2 hours, 2 minutes", "1 day, 2 hours, 2 minutes"); + $this->t->is(expiration_duration(60*60*24*2 + 60*60 + 60), "2 days, 1 hour, 1 minute", "2 days, 1 hour, 1 minute"); + $this->t->is(expiration_duration(60*60*24*2 + 60*60 + 120), "2 days, 1 hour, 2 minutes", "2 days, 1 hour, 2 minutes"); + $this->t->is(expiration_duration(60*60*24*2 + 60*60*2 + 60), "2 days, 2 hours, 1 minute", "2 days, 2 hours, 1 minute"); + $this->t->is(expiration_duration(60*60*24*2 + 60*60*2 + 120), "2 days, 2 hours, 2 minutes", "2 days, 2 hours, 2 minutes"); + + $this->t->is(expiration_duration(60*60*24 + 60*60), "1 day, 1 hour", "1 day, 1 hour"); + $this->t->is(expiration_duration(60*60*24 + 60*60*2), "1 day, 2 hours", "1 day, 2 hours"); + $this->t->is(expiration_duration(60*60*24*2 + 60*60), "2 days, 1 hour", "2 days, 1 hour"); + $this->t->is(expiration_duration(60*60*24*2 + 60*60*2), "2 days, 2 hours", "2 days, 2 hours"); + + $this->t->is(expiration_duration(60*60*24 + 60), "1 day, 1 minute", "1 day, 1 minute"); + $this->t->is(expiration_duration(60*60*24 + 120), "1 day, 2 minutes", "1 day, 2 minutes"); + $this->t->is(expiration_duration(60*60*24*2 + 60), "2 days, 1 minute", "2 days, 1 minute"); + $this->t->is(expiration_duration(60*60*2*24 + 120), "2 days, 2 minutes", "2 days, 2 minutes"); + + $this->t->is(expiration_duration(60*60 + 60), "1 hour, 1 minute", "1 hour, 1 minute"); + $this->t->is(expiration_duration(60*60 + 120), "1 hour, 2 minutes", "1 hour, 2 minutes"); + $this->t->is(expiration_duration(60*60*2 + 60), "2 hours, 1 minute", "2 hours, 1 minute"); + $this->t->is(expiration_duration(60*60*2 + 120), "2 hours, 2 minutes", "2 hours, 2 minutes"); + + $this->t->is(expiration_duration(61), "1 minute, 1 second", "1 minute, 1 second"); + $this->t->is(expiration_duration(62), "1 minute, 2 seconds", "1 minute, 2 seconds"); + $this->t->is(expiration_duration(121), "2 minutes, 1 second", "2 minutes, 1 second"); + $this->t->is(expiration_duration(122), "2 minutes, 2 seconds", "2 minutes, 2 seconds"); + + $this->t->is(expiration_duration(60*60*24 + 60*60*23 + 60*59), "1 day, 23 hours, 59 minutes", "1 day, 23 hours, 59 minutes"); + $this->t->is(expiration_duration(60*60*23 + 60*59), "23 hours, 59 minutes", "23 hours, 59 minutes"); + $this->t->is(expiration_duration(60*60*2 + 60*59), "2 hours, 59 minutes", "2 hours, 59 minutes"); + } + + public function test_format_bytes() + { + $this->t->is(format_bytes(500), "500B", "500B"); + $this->t->is(format_bytes(1500), "1500B", "1500B"); + $this->t->is(format_bytes(1500*1024), "1500.00KiB", "1500.00KiB"); + $this->t->is(format_bytes(1500*1024*1024), "1500.00MiB", "1500.00MiB"); + $this->t->is(format_bytes(1500*1024*1024*1024), "1500.00GiB", "1500.00GiB"); + $this->t->is(format_bytes(1500*1024*1024*1024*1024), "1500.00TiB", "1500.00TiB"); + $this->t->is(format_bytes(1500*1024*1024*1024*1024*1024), "1500.00PiB", "1500.00PiB"); + } + + public function test_files_are_equal() + { + $a1 = FCPATH.'/data/tests/message1.bin'; + $a2 = FCPATH.'/data/tests/message2.bin'; + $b = FCPATH.'/data/tests/simple.pdf'; + $this->t->is(files_are_equal($a1, $a2), false, "Same hash, but different file"); + $this->t->is(files_are_equal($a1, $b), false, "Different filesize"); + $this->t->is(files_are_equal($a1, $a1), true, "Same file"); + $this->t->is(files_are_equal($a2, $a2), true, "Same file"); + } + + public function test_return_bytes() + { + $this->t->is(return_bytes("1k"), 1*1024, "1k"); + $this->t->is(return_bytes("1M"), 1*1024*1024, "1M"); + $this->t->is(return_bytes("1G"), 1*1024*1024*1024, "1G"); + + try { + return_bytes("1P"); + } catch (\exceptions\ApiException $e) { + $this->t->is($e->get_error_id(), 'filebin-helper/invalid-input-unit', "unhandled text: 1P"); + } + + $this->t->is(return_bytes("106954752"), 106954752, "value without unit is returned as int"); + } +} diff --git a/application/test/tests/test_libraries_exif.php b/application/test/tests/test_libraries_exif.php new file mode 100644 index 000000000..3ca821c03 --- /dev/null +++ b/application/test/tests/test_libraries_exif.php @@ -0,0 +1,48 @@ +<?php +/* + * Copyright 2016 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +namespace test\tests; + +class test_libraries_exif extends \test\Test { + + public function __construct() + { + parent::__construct(); + } + + public function init() + { + } + + public function cleanup() + { + } + + public function test_get_exif_jpeg() + { + $ret = \libraries\Exif::get_exif(FCPATH.'/data/tests/exif-orientation-examples/Portrait_1.jpg'); + + $this->t->is($ret['Orientation'], 1, "Get correct EXIF Orientation"); + $this->t->is($ret['FileName'], "Portrait_1.jpg", "Get correct EXIF FileName"); + } + + public function test_get_exif_invalidTypes() + { + $ret = \libraries\Exif::get_exif(FCPATH.'/data/tests/black_white.png'); + $this->t->is($ret, false, "PNG not supported"); + } + + public function test_get_exif_missingFile() + { + $ret = \libraries\Exif::get_exif(FCPATH.'/data/tests/thisFileDoesNotExist'); + $this->t->is($ret, false, "Should return false for missing file"); + } + +} + diff --git a/application/test/tests/test_libraries_image.php b/application/test/tests/test_libraries_image.php new file mode 100644 index 000000000..d6afc64df --- /dev/null +++ b/application/test/tests/test_libraries_image.php @@ -0,0 +1,110 @@ +<?php +/* + * Copyright 2015 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +namespace test\tests; + +class test_libraries_image extends \test\Test { + + public function __construct() + { + parent::__construct(); + } + + public function init() + { + } + + public function cleanup() + { + } + + public function test_type_supported_normalCase() + { + $this->t->is(\libraries\Image::type_supported('image/png'), true, 'image/png should be supported'); + $this->t->is(\libraries\Image::type_supported('image/jpeg'), true, 'image/jpeg should be supported'); + + $this->t->is(\libraries\Image::type_supported('application/pdf'), false, 'application/pdf should not be supported'); + $this->t->is(\libraries\Image::type_supported('application/octet-stream'), false, 'application/octet-stream should not be supported'); + $this->t->is(\libraries\Image::type_supported('text/plain'), false, 'text/plain should not be supported'); + } + + public function test_makeThumb_PNG() + { + $img = new \libraries\Image(FCPATH."/data/tests/black_white.png"); + $img->makeThumb(150, 150); + $thumb = $img->get(IMAGETYPE_PNG); + + $this->t->ok($thumb !== "", "Got thumbnail"); + } + + public function test_makeThumb_PDF() + { + try { + $img = new \libraries\Image(FCPATH."/data/tests/simple.pdf"); + $this->t->fail("PDF should not be supported"); + $img->makeThumb(150, 150); + $thumb = $img->get(IMAGETYPE_JPEG); + $this->t->ok($thumb !== "", "Got thumbnail"); + } catch (\exceptions\PublicApiException $e) { + $correct_error = $e->get_error_id() == "libraries/Image/unsupported-image-type"; + $this->t->ok($correct_error, "Should get exception"); + if (!$correct_error) { + // @codeCoverageIgnoreStart + throw $e; + // @codeCoverageIgnoreEnd + } + } + } + + public function test_makeThumb_binaryFile() + { + try { + $img = new \libraries\Image(FCPATH."/data/tests/message1.bin"); + } catch (\exceptions\PublicApiException $e) { + $correct_error = $e->get_error_id() == "libraries/Image/unsupported-image-type"; + $this->t->ok($correct_error, "Should get exception"); + if (!$correct_error) { + // @codeCoverageIgnoreStart + throw $e; + // @codeCoverageIgnoreEnd + } + } + } + + public function test_get_exif_orientation() + { + $ret = \libraries\Image::get_exif_orientation(FCPATH."/data/tests/black_white.png"); + $this->t->is($ret, 0, "Got correct Orientation for image without orientation information"); + + foreach ([1,2,3,4,5,6,7,8] as $orientation) { + $ret = \libraries\Image::get_exif_orientation(FCPATH."/data/tests/exif-orientation-examples/Landscape_$orientation.jpg"); + $this->t->is($ret, $orientation, "Got correct Orientation for Landscape_$orientation.jpg"); + + $ret = \libraries\Image::get_exif_orientation(FCPATH."/data/tests/exif-orientation-examples/Portrait_$orientation.jpg"); + $this->t->is($ret, $orientation, "Got correct Orientation for Portrait_$orientation.jpg"); + } + } + + public function test_makeThumb_differentOrientation() + { + foreach ([1,2,3,4,5,6,7,8] as $orientation) { + $img = new \libraries\Image(FCPATH."/data/tests/exif-orientation-examples/Landscape_$orientation.jpg"); + $img->makeThumb(100, 100); + $thumb = $img->get(); + $this->t->ok($thumb != '', "Got thumbnail for Landscape_$orientation.jpg"); + + $img = new \libraries\Image(FCPATH."/data/tests/exif-orientation-examples/Portrait_$orientation.jpg"); + $img->makeThumb(100, 100); + $thumb = $img->get(); + $this->t->ok($thumb != '', "Got thumbnail for Portrait_$orientation.jpg"); + } + } + +} + diff --git a/application/test/tests/test_libraries_output_cache.php b/application/test/tests/test_libraries_output_cache.php new file mode 100644 index 000000000..3668bc6b4 --- /dev/null +++ b/application/test/tests/test_libraries_output_cache.php @@ -0,0 +1,82 @@ +<?php +/* + * Copyright 2016 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +namespace test\tests; + +class test_libraries_output_cache extends \test\Test { + + public function __construct() + { + parent::__construct(); + } + + public function init() + { + } + + public function cleanup() + { + } + + public function test_add() + { + $oc = new \libraries\Output_cache(); + $oc->add("teststring"); + + ob_start(); + $oc->render(); + $output = ob_get_clean(); + + $this->t->is($output, "teststring", "Simple add renders correctly"); + } + + public function test_add_function() + { + $oc = new \libraries\Output_cache(); + $oc->add_function(function() {echo "teststring";}); + + ob_start(); + $oc->render(); + $output = ob_get_clean(); + + $this->t->is($output, "teststring", "Simple add_function renders correctly"); + } + + public function test_add_merge() + { + + $oc = new \libraries\Output_cache(); + $oc->add_merge(['items' => ["test1\n"]], 'tests/echo-fragment'); + $oc->add_merge(['items' => ["test2\n"]], 'tests/echo-fragment'); + + ob_start(); + $oc->render(); + $output = ob_get_clean(); + + $this->t->is($output, "listing 2 items:\ntest1\ntest2\n", "Simple add renders correctly"); + } + + public function test_add_merge_mixedViews() + { + + $oc = new \libraries\Output_cache(); + $oc->add_merge(['items' => ["test1\n"]], 'tests/echo-fragment'); + $oc->add_merge(['items' => ["test2\n"]], 'tests/echo-fragment'); + $oc->add("blub\n"); + $oc->add_merge(['items' => ["test3\n"]], 'tests/echo-fragment'); + + ob_start(); + $oc->render(); + $output = ob_get_clean(); + + $this->t->is($output, "listing 2 items:\ntest1\ntest2\nblub\nlisting 1 items:\ntest3\n", "Simple add renders correctly"); + } + +} + diff --git a/application/test/tests/test_libraries_procrunner.php b/application/test/tests/test_libraries_procrunner.php new file mode 100644 index 000000000..daac8a2bc --- /dev/null +++ b/application/test/tests/test_libraries_procrunner.php @@ -0,0 +1,122 @@ +<?php +/* + * Copyright 2015 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +namespace test\tests; + +class test_libraries_procrunner extends \test\Test { + + public function __construct() + { + parent::__construct(); + } + + public function init() + { + } + + public function cleanup() + { + } + + public function test_exec_true() + { + $p = new \libraries\ProcRunner(['true']); + $ret = $p->exec(); + + $this->t->is($ret['stderr'], '', 'stderr should be empty'); + $this->t->is($ret['stdout'], '', 'stdout should be empty'); + $this->t->is($ret['return_code'], 0, 'return code should be 0'); + } + + public function test_exec_false() + { + $p = new \libraries\ProcRunner(['false']); + $ret = $p->exec(); + + $this->t->is($ret['stderr'], '', 'stderr should be empty'); + $this->t->is($ret['stdout'], '', 'stdout should be empty'); + $this->t->is($ret['return_code'], 1, 'return code should be 1'); + } + + public function test_exec_nonexistent() + { + $p = new \libraries\ProcRunner(['thisCommandDoesNotExist']); + $ret = $p->exec(); + + if (PHP_MAJOR_VERSION >= 8) { + $this->t->is($ret['stderr'], "sh: line 1: thisCommandDoesNotExist: command not found\n", 'stderr should be empty'); + } else { + $this->t->is($ret['stderr'], "sh: thisCommandDoesNotExist: command not found\n", 'stderr should be empty'); + } + $this->t->is($ret['stdout'], '', 'stdout should be empty'); + $this->t->is($ret['return_code'], 127, 'return code should be 127'); + } + + public function test_exec_tac() + { + + $line1 = "this is the first line"; + $line2 = "and this is the second one"; + $input = "$line1\n$line2\n"; + $output = "$line2\n$line1\n"; + + $p = new \libraries\ProcRunner(['tac']); + $p->input($input); + $ret = $p->exec(); + + $this->t->is($ret['stderr'], '', 'stderr should be empty'); + $this->t->is($ret['stdout'], $output, 'stdout should be reversed'); + $this->t->is($ret['return_code'], 0, 'return code should be 0'); + } + + public function test_forbid_nonzero() + { + $p = new \libraries\ProcRunner(['false']); + $p->forbid_nonzero(); + + try { + $p->exec(); + $this->t->ok(false, "this should have caused an an exception"); + } catch (\exceptions\ApiException $e) { + $this->t->is($e->get_error_id(), 'procrunner/non-zero-exit', "correct exception triggered"); + $this->t->is_deeply($e->get_data(), [ + "'false'", + null, + [ + 'return_code' => 1, + 'stdout' => '', + 'stderr' => '', + ], + ], "correct exception data"); + } + } + + public function test_forbid_stderr() + { + $p = new \libraries\ProcRunner(['bash', '-c', 'echo "This is a test error message" >&2; exit 2;']); + $p->forbid_stderr(); + + try { + $p->exec(); + $this->t->ok(false, "this should have caused an an exception"); + } catch (\exceptions\ApiException $e) { + $this->t->is($e->get_error_id(), 'procrunner/stderr', "correct exception triggered"); + $this->t->is_deeply($e->get_data(), [ + "'bash' '-c' 'echo \"This is a test error message\" >&2; exit 2;'", + null, + [ + 'return_code' => 2, + 'stdout' => '', + 'stderr' => "This is a test error message\n", + ], + ], "correct exception data"); + } + } +} + diff --git a/application/test/tests/test_libraries_pygments.php b/application/test/tests/test_libraries_pygments.php new file mode 100644 index 000000000..2e6e8447f --- /dev/null +++ b/application/test/tests/test_libraries_pygments.php @@ -0,0 +1,115 @@ +<?php +/* + * Copyright 2015 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +namespace test\tests; + +class test_libraries_pygments extends \test\Test { + + public function __construct() + { + parent::__construct(); + } + + public function init() + { + } + + public function cleanup() + { + } + + public function test_autodetect_lexer_normalCase() + { + $p = new \libraries\Pygments('/invalid/filepath', 'text/plain', 'stdin'); + $this->t->is($p->autodetect_lexer(), 'text', "text/plain should be text"); + + $p = new \libraries\Pygments('/invalid/filepath', 'application/x-php', 'stdin'); + $this->t->is($p->autodetect_lexer(), 'php', "application/php should be php"); + + // This is from pygments and not our hardcoded list + $p = new \libraries\Pygments('/invalid/filepath', 'text/x-pascal', 'stdin'); + $this->t->is($p->autodetect_lexer(), 'delphi', "text/x-pascal should be delphi"); + + $p = new \libraries\Pygments('/invalid/filepath', 'application/octet-stream', 'stdin'); + $this->t->is($p->autodetect_lexer(), false, "application/octet-stream should return false"); + } + + public function test_autodetect_lexer_specialFilenames() + { + $p = new \libraries\Pygments('/invalid/filepath', 'text/plain', 'foo.c'); + $this->t->is($p->autodetect_lexer(), 'c', "foo.c should be c"); + + $p = new \libraries\Pygments('/invalid/filepath', 'text/plain', 'PKGBUILD'); + $this->t->is($p->autodetect_lexer(), 'bash', "PKGBUILD should be bash"); + + $p = new \libraries\Pygments('/invalid/filepath', 'text/plain', 'asciinema.json'); + $this->t->is($p->autodetect_lexer(), 'asciinema', "asciinema.json should be asciinema"); + + $p = new \libraries\Pygments('/invalid/filepath', 'text/plain', 'test.asciinema.json'); + $this->t->is($p->autodetect_lexer(), 'asciinema', "asciinema.json should be asciinema"); + } + + public function test_autodetect_lexer_specialFilenamesBinaryShouldNotHighlight() + { + $p = new \libraries\Pygments('/invalid/filepath', 'application/octet-stream', 'foo.c'); + $this->t->is($p->autodetect_lexer(), false, "foo.c should not highlight if binary"); + + $p = new \libraries\Pygments('/invalid/filepath', 'application/octet-stream', 'PKGBUILD'); + $this->t->is($p->autodetect_lexer(), false, "PKGBUILD should not highlight if binary"); + } + + public function test_can_highlight_normalCase() + { + $p = new \libraries\Pygments('/invalid/filepath', 'text/plain', 'stdin'); + $this->t->is($p->can_highlight(), true, "text/plain can highlight"); + + $p = new \libraries\Pygments('/invalid/filepath', 'application/x-php', 'stdin'); + $this->t->is($p->can_highlight(), true, "application/x-php can highlight"); + + $p = new \libraries\Pygments('/invalid/filepath', 'application/octet-stream', 'stdin'); + $this->t->is($p->can_highlight(), false, "application/octet-stream can not highlight"); + } + + public function test_autodetect_lexer_canButShouldntHighlight() + { + $p = new \libraries\Pygments('/invalid/filepath', 'image/svg+xml', 'foo.svg'); + $this->t->is($p->autodetect_lexer(), false, "image/svg+xml should return false"); + } + + public function test_can_highlight_canButShouldntHighlight() + { + $p = new \libraries\Pygments('/invalid/filepath', 'image/svg+xml', 'foo.svg'); + $this->t->is($p->can_highlight(), true, "image/svg+xml can highlight"); + } + + public function test_autodetect_lexer_strangeFilenames() + { + $p = new \libraries\Pygments('/invalid/filepath', 'text/plain', 'foo.'); + $this->t->is($p->autodetect_lexer(), 'text', "foo. should be text"); + + } + + public function test_get_lexers() + { + $l = \libraries\Pygments::get_lexers(); + + $this->t->is($l['text'], 'Plain text', 'Plain text lexer exists'); + $this->t->is($l['c'], 'C', 'C lexer exists'); + } + + public function test_resolve_lexer_alias() + { + $p = new \libraries\Pygments('/invalid/filepath', 'text/plain', 'foo.pl'); + $this->t->is($p->resolve_lexer_alias('pl'), 'perl', "Test pl alias for perl"); + + $this->t->is($p->resolve_lexer_alias('thisIsInvalid'), 'thisIsInvalid', "Test invalid alias"); + } + +} + diff --git a/application/test/tests/test_libraries_tempfile.php b/application/test/tests/test_libraries_tempfile.php new file mode 100644 index 000000000..f4c10a22e --- /dev/null +++ b/application/test/tests/test_libraries_tempfile.php @@ -0,0 +1,46 @@ +<?php +/* + * Copyright 2015 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +namespace test\tests; + +class test_libraries_tempfile extends \test\Test { + + public function __construct() + { + parent::__construct(); + } + + public function init() + { + } + + public function cleanup() + { + } + + public function test_destructor_normalCase() + { + $t = new \libraries\Tempfile(); + $file = $t->get_file(); + $this->t->is(file_exists($file), true, "file should exist"); + unset($t); + $this->t->is(file_exists($file), false, "file should no longer exist after destruction of object"); + } + + public function test_destructor_alreadyRemoved() + { + $t = new \libraries\Tempfile(); + $file = $t->get_file(); + $this->t->is(file_exists($file), true, "file should exist"); + unlink($file); + $this->t->is(file_exists($file), false, "file deleted"); + unset($t); + $this->t->is(file_exists($file), false, "file should no longer exist after destruction of object"); + } +} diff --git a/application/test/tests/test_models_muser.php b/application/test/tests/test_models_muser.php new file mode 100644 index 000000000..af0e58834 --- /dev/null +++ b/application/test/tests/test_models_muser.php @@ -0,0 +1,113 @@ +<?php +/* + * Copyright 2016 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +namespace test\tests; + +class test_models_muser extends \test\Test { + + public function __construct() + { + parent::__construct(); + } + + public function init() + { + } + + public function cleanup() + { + } + + public function test_valid_username() + { + $CI =& get_instance(); + + $this->t->is($CI->muser->valid_username("thisisbob42"), true, "valid username"); + $this->t->is($CI->muser->valid_username("31337"), true, "valid username"); + $this->t->is($CI->muser->valid_username("thisisjoe"), true, "valid username"); + $this->t->is($CI->muser->valid_username("1234567890123456789012345678901"), true, "31 chars"); + $this->t->is($CI->muser->valid_username("12345678901234567890123456789012"), true, "32 chars"); + + $this->t->is($CI->muser->valid_username("Joe"), false, "contains uppercase"); + $this->t->is($CI->muser->valid_username("joe_bob"), false, "contains underscore"); + $this->t->is($CI->muser->valid_username("joe-bob"), false, "contains dash"); + $this->t->is($CI->muser->valid_username("123456789012345678901234567890123"), false, "33 chars"); + $this->t->is($CI->muser->valid_username("1234567890123456789012345678901234"), false, "34 chars"); + } + + public function test_valid_email() + { + $CI =& get_instance(); + + $this->t->is($CI->muser->valid_email("joe@bob.com"), true, "valid email"); + $this->t->is($CI->muser->valid_email("joe+mailbox@bob.com"), true, "valid email"); + $this->t->is($CI->muser->valid_email("bob@fancyaddress.net"), true, "valid email"); + + $this->t->is($CI->muser->valid_email("joebob.com"), false, "missing @"); + } + + public function test_delete_user() + { + $CI =& get_instance(); + $CI->muser->add_user("userdeltest1", "supersecret", "tester@localhost.lan", null); + $this->t->is($CI->muser->username_exists("userdeltest1"), true, "User should exist after creation"); + + $ret = $CI->muser->delete_user("userdeltest1", "wrongpassword"); + $this->t->is($ret, false, "Deletion should fail with incorrect password"); + + $ret = $CI->muser->delete_user("userdeltest1", ""); + $this->t->is($ret, false, "Deletion should fail with empty password"); + + $this->t->is($CI->muser->username_exists("userdeltest1"), true, "User should exist after failed deletions"); + + $ret = $CI->muser->delete_user("userdeltest1", "supersecret"); + $this->t->is($ret, true, "Deletion should succeed with correct data"); + $this->t->is($CI->muser->username_exists("userdeltest1"), false, "User should not exist after deletion"); + } + + public function test_delete_user_verifyFilesDeleted() + { + $CI =& get_instance(); + + $id = "testid1"; + $id2 = "testid2"; + $content = "test content"; + $filename = "some cool name"; + $username = "testuser1"; + $password = "testpass"; + + $CI->muser->add_user($username, $password, "tester@localhost.lan", null); + $userid = $CI->muser->get_userid_by_name($username); + + $CI->muser->add_user("joe", "joeisawesome", "tester2@localhost.lan", null); + $userid2 = $CI->muser->get_userid_by_name("joe"); + + \service\files::add_file_data($userid, $id, $content, $filename); + \service\files::add_file_data($userid2, $id2, $content, $filename); + + $mid = \service\files::create_multipaste([$id], $userid, [3,6])['url_id']; + $mid2 = \service\files::create_multipaste([$id2], $userid2, [3,6])['url_id']; + + $this->t->is($CI->mfile->id_exists($id), true, "File exists after being added"); + $this->t->is($CI->mmultipaste->id_exists($mid), true, "Multipaste exists after creation"); + $this->t->is($CI->mfile->id_exists($id2), true, "File2 exists after being added"); + $this->t->is($CI->mmultipaste->id_exists($mid2), true, "Multipaste2 exists after creation"); + + $ret = $CI->muser->delete_user($username, $password); + $this->t->is($ret, true, "Delete user"); + + $this->t->is($CI->mfile->id_exists($id), false, "File should be gone after deletion of user"); + $this->t->is($CI->mmultipaste->id_exists($mid), false, "Multipaste should be gone after deletion of user"); + $this->t->is($CI->mfile->id_exists($id2), true, "File2 owned by different user should still exist after deletion from other user"); + $this->t->is($CI->mmultipaste->id_exists($mid2), true, "Multipaste2 owned by different user should still exist after deletion from other user"); + } + + +} + diff --git a/application/test/tests/test_service_files.php b/application/test/tests/test_service_files.php new file mode 100644 index 000000000..b9d6fce66 --- /dev/null +++ b/application/test/tests/test_service_files.php @@ -0,0 +1,121 @@ +<?php +/* + * Copyright 2015 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +namespace test\tests; + +class test_service_files extends \test\Test { + + public function __construct() + { + parent::__construct(); + + $CI =& get_instance(); + $CI->load->model("muser"); + $CI->load->model("mfile"); + + } + + public function test_verify_uploaded_files_noFiles() + { + $a = array(); + try { + \service\files::verify_uploaded_files($a); + // @codeCoverageIgnoreStart + $this->t->fail("verify should error"); + // @codeCoverageIgnoreEnd + } catch (\exceptions\UserInputException $e) { + $this->t->is($e->get_error_id(), "file/no-file", "verify should error"); + } + } + + public function test_verify_uploaded_files_normal() + { + $CI =& get_instance(); + $a = array( + array( + "name" => "foobar.txt", + "type" => "text/plain", + "tmp_name" => NULL, + "error" => UPLOAD_ERR_OK, + "size" => 1, + "formfield" => "file[1]", + ) + ); + + \service\files::verify_uploaded_files($a); + $this->t->pass("verify should work"); + } + + public function test_verify_uploaded_files_uploadError() + { + $CI =& get_instance(); + $a = array( + array( + "name" => "foobar.txt", + "type" => "text/plain", + "tmp_name" => NULL, + "error" => UPLOAD_ERR_NO_FILE, + "size" => 1, + "formfield" => "file[1]", + ) + ); + + try { + \service\files::verify_uploaded_files($a); + // @codeCoverageIgnoreStart + $this->t->fail("verify should error"); + // @codeCoverageIgnoreEnd + } catch (\exceptions\UserInputException $e) { + $data = $e->get_data(); + $this->t->is($e->get_error_id(), "file/upload-verify", "verify should error"); + $this->t->is_deeply(array( + 'file[1]' => array( + 'filename' => 'foobar.txt', + 'formfield' => 'file[1]', + 'message' => 'No file was uploaded', + ), + ), $data, "expected data in exception"); + } + } + + public function test_ellipsize() + { + $a1 = "abc"; + $a2 = "abc\nabc"; + $a3 = "abc\nabc\nabc"; + $a4 = "abc\nabc\nabc\nabc"; + + $this->t->is(\service\files::ellipsize($a1, 1, strlen($a1)), + $a1, "Trim 1 line to 1, no change"); + + $this->t->is(\service\files::ellipsize($a3, 3, strlen($a3)), + $a3, "Trim 3 lines to 3, no change"); + + $this->t->is(\service\files::ellipsize($a3, 5, strlen($a3)), + $a3, "Trim 3 lines to 5, no change"); + + $this->t->is(\service\files::ellipsize($a2, 1, strlen($a2)), + "$a1\n...", "Trim 2 lines to 1, drop one line"); + + $this->t->is(\service\files::ellipsize($a3, 2, strlen($a3)), + "$a2\n...", "Trim 3 lines to 2, drop one line"); + + $this->t->is(\service\files::ellipsize($a4, 2, strlen($a4)), + "$a2\n...", "Trim 4 lines to 2, drop 2 lines"); + + $this->t->is(\service\files::ellipsize($a3, 3, strlen($a3) + 1), + "$a2\n...", "Last line incomplete, drop one line"); + + $this->t->is(\service\files::ellipsize($a1, 5, strlen($a1) + 1), + "$a1 ...", "Single line incomplete, only add dots"); + } + + +} + diff --git a/application/test/tests/test_service_files_valid_id.php b/application/test/tests/test_service_files_valid_id.php new file mode 100644 index 000000000..24886be43 --- /dev/null +++ b/application/test/tests/test_service_files_valid_id.php @@ -0,0 +1,115 @@ +<?php +/* + * Copyright 2015 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +namespace test\tests; + +class test_service_files_valid_id extends \test\Test { + private $model; + private $filedata; + private $config; + + public function __construct() + { + parent::__construct(); + + $CI =& get_instance(); + $CI->load->model("muser"); + $CI->load->model("mfile"); + + } + + public function init() + { + $this->model = \Mockery::mock("Mfile"); + $this->model->shouldReceive("delete_id")->never()->byDefault(); + $this->model->shouldReceive("delete_data_id")->never()->byDefault(); + $this->model->shouldReceive("file")->with("file-hash-1-1")->andReturn("/invalid/path/file-1")->byDefault(); + $this->model->shouldReceive("filemtime")->with("/invalid/path/file-1")->andReturn(500)->byDefault(); + $this->model->shouldReceive("filesize")->with("/invalid/path/file-1")->andReturn(50*1024)->byDefault(); + $this->model->shouldReceive("file_exists")->with("/invalid/path/file-1")->andReturn(true)->byDefault(); + + $this->filedata = array( + "data_id" => "file-hash-1-1", + "hash" => "file-hash-1", + "id" => "file-id-1", + "user" => 2, + "date" => 500, + ); + + $this->config = array( + "upload_max_age" => 20, + "sess_expiration" => 10, + "small_upload_size" => 10*1024, + ); + } + + public function cleanup() + { + \Mockery::close(); + } + + public function test_valid_id_keepNormalUpload() + { + $ret = \service\files::valid_id($this->filedata, $this->config, $this->model, 505); + $this->t->is($ret, true, "normal case should be valid"); + } + + public function test_valid_id_keepSmallUpload() + { + $this->model->shouldReceive("filesize")->with("/invalid/path/file-1")->once()->andReturn(50); + + $ret = \service\files::valid_id($this->filedata, $this->config, $this->model, 550); + $this->t->is($ret, true, "file is old, but small and should be kept"); + } + + public function test_valid_id_removeOldFile() + { + $this->model->shouldReceive("delete_data_id")->with("file-hash-1-1")->once(); + + $ret = \service\files::valid_id($this->filedata, $this->config, $this->model, 550); + $this->t->is($ret, false, "file is old and should be removed"); + } + + public function test_valid_id_removeOldUpload() + { + $this->model->shouldReceive("delete_id")->with("file-id-1")->once(); + $this->model->shouldReceive("filemtime")->with("/invalid/path/file-1")->once()->andReturn(540); + + $ret = \service\files::valid_id($this->filedata, $this->config, $this->model, 550); + $this->t->is($ret, false, "upload is old and should be removed"); + } + + public function test_valid_id_keepNormalUnownedFile() + { + $this->filedata["user"] = 0; + + $ret = \service\files::valid_id($this->filedata, $this->config, $this->model, 505); + $this->t->is($ret, true, "upload is unowned and should be kept"); + } + + public function test_valid_id_removeOldUnownedFile() + { + $this->model->shouldReceive("delete_id")->with("file-id-1")->once(); + $this->filedata["user"] = 0; + + $ret = \service\files::valid_id($this->filedata, $this->config, $this->model, 515); + $this->t->is($ret, false, "upload is old, unowned and should be removed"); + } + + public function test_valid_id_removeMissingFile() + { + $this->model->shouldReceive("file_exists")->with("/invalid/path/file-1")->once()->andReturn(false); + $this->model->shouldReceive("delete_data_id")->with("file-hash-1-1")->once(); + + $ret = \service\files::valid_id($this->filedata, $this->config, $this->model, 505); + $this->t->is($ret, false, "missing file should be removed"); + } + +} + diff --git a/application/test/tests/test_service_multipaste_queue.php b/application/test/tests/test_service_multipaste_queue.php new file mode 100644 index 000000000..cab53e335 --- /dev/null +++ b/application/test/tests/test_service_multipaste_queue.php @@ -0,0 +1,93 @@ +<?php +/* + * Copyright 2016 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +namespace test\tests; + +class test_service_multipaste_queue extends \test\Test { + + public function __construct() + { + parent::__construct(); + } + + public function init() + { + $this->session = \Mockery::mock("Session"); + $this->session->shouldReceive("userdata")->never()->byDefault(); + $this->session->shouldReceive("set_userdata")->never()->byDefault(); + + $this->mfile = \Mockery::mock("Mfile"); + $this->mfile->shouldReceive("valid_id")->never()->byDefault(); + + $this->mmultipaste = \Mockery::mock("Mmultipaste"); + $this->mmultipaste->shouldReceive("valid_id")->never()->byDefault(); + + $this->m = new \service\multipaste_queue($this->session, $this->mfile, $this->mmultipaste); + } + + public function cleanup() + { + \Mockery::close(); + } + + public function test_get() + { + $this->session->shouldReceive('userdata')->with("multipaste_queue")->once()->andReturn(null); + $this->t->is_deeply($this->m->get(), [], "Fresh queue is empty"); + } + + public function test_set() + { + $this->session->shouldReceive('set_userdata')->with("multipaste_queue", ['abc', '123'])->once(); + + $this->mfile->shouldReceive('valid_id')->with('abc')->once()->andReturn(true); + $this->mfile->shouldReceive('valid_id')->with('123')->once()->andReturn(true); + + $this->t->is($this->m->set(['abc', '123']), null, "set() should succeed"); + } + + public function test_append() + { + $this->session->shouldReceive('userdata')->with("multipaste_queue")->once()->andReturn(null); + $this->mfile->shouldReceive('valid_id')->with('abc')->times(2)->andReturn(true); + $this->session->shouldReceive('set_userdata')->with("multipaste_queue", ['abc'])->once(); + $this->t->is($this->m->append(['abc']), null, "append([abc]) should succeed"); + + $this->session->shouldReceive('userdata')->with("multipaste_queue")->once()->andReturn(['abc']); + $this->mfile->shouldReceive('valid_id')->with('123')->once()->andReturn(true); + $this->session->shouldReceive('set_userdata')->with("multipaste_queue", ['abc', '123'])->once(); + $this->t->is($this->m->append(['123']), null, "append([123]) should succeed"); + } + + public function test_append_itemAlreadyInQueue() + { + $this->session->shouldReceive('userdata')->with("multipaste_queue")->once()->andReturn(['abc', '123']); + $this->mfile->shouldReceive('valid_id')->with('abc')->once()->andReturn(true); + $this->mfile->shouldReceive('valid_id')->with('123')->once()->andReturn(true); + $this->session->shouldReceive('set_userdata')->with("multipaste_queue", ['abc', '123'])->once(); + $this->t->is($this->m->append(['abc']), null, "append([abc]) should succeed"); + } + + public function test_append_multipaste() + { + $this->session->shouldReceive('userdata')->with("multipaste_queue")->once()->andReturn([]); + $this->mmultipaste->shouldReceive('valid_id')->with('m-abc')->once()->andReturn(true); + $this->mmultipaste->shouldReceive('get_files')->with('m-abc')->once()->andReturn([ + ['id' => 'abc'], + ['id' => '123'], + ]); + $this->mfile->shouldReceive('valid_id')->with('abc')->once()->andReturn(true); + $this->mfile->shouldReceive('valid_id')->with('123')->once()->andReturn(true); + $this->session->shouldReceive('set_userdata')->with("multipaste_queue", ['abc', '123'])->once(); + $this->t->is($this->m->append(['m-abc']), null, "append([m-abc]) should succeed"); + } + + +} + diff --git a/application/test/tests/test_service_storage.php b/application/test/tests/test_service_storage.php new file mode 100644 index 000000000..83284ea4b --- /dev/null +++ b/application/test/tests/test_service_storage.php @@ -0,0 +1,172 @@ +<?php +/* + * Copyright 2016 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +namespace test\tests; + +class test_service_storage extends \test\Test { + + private $tempdir; + + public function __construct() + { + parent::__construct(); + } + + public function init() + { + $this->tempdir = trim((new \libraries\ProcRunner(['mktemp', '-d']))->execSafe()['stdout']); + } + + public function cleanup() + { + rmdir($this->tempdir); + } + + public function test_normalCase() + { + $file = $this->tempdir.'/testfile1'; + $storage = new \service\storage($file); + + $this->t->is($storage->get_file(), $file, "get_file returns correct path"); + + $a = $storage->begin(); + file_put_contents($a, "teststring1"); + $this->t->is(file_exists($file), false, "Test file doesn't exist yet"); + $this->t->is($storage->exists(), false, "Test file doesn't exist yet"); + + $storage->commit(); + $this->t->is(file_exists($file), true, "Test file has been created"); + $this->t->is(file_get_contents($file), 'teststring1', "Test file has correct content"); + + unlink($file); + } + + public function test_existingFile() + { + $file = $this->tempdir.'/testfile-existing-file'; + file_put_contents($file, "teststring-old"); + + $storage = new \service\storage($file); + + $a = $storage->begin(); + file_put_contents($a, "teststring-changed"); + $this->t->is(file_exists($file), true, "Test file already exists"); + $this->t->is(file_get_contents($file), 'teststring-old', "Test file has old content"); + + $storage->commit(); + $this->t->is(file_exists($file), true, "Test file still exists"); + $this->t->is(file_get_contents($file), 'teststring-changed', "Test file has updated content"); + + unlink($file); + } + + public function test_rollback() + { + $file = $this->tempdir.'/testfile-rollback'; + file_put_contents($file, "teststring-old"); + + $storage = new \service\storage($file); + + $a = $storage->begin(); + file_put_contents($a, "teststring-changed"); + $this->t->is(file_exists($file), true, "Test file already exists"); + $this->t->is(file_get_contents($file), 'teststring-old', "Test file has old content"); + + $storage->rollback(); + $this->t->is(file_exists($file), true, "Test file still exists"); + $this->t->is(file_get_contents($file), 'teststring-old', "Test file still has old content"); + + unlink($file); + } + + public function test_gzip_compress() + { + $file = $this->tempdir.'/testfile-gzip'; + file_put_contents($file, "teststring-old"); + + $storage = new \service\storage($file); + + $a = $storage->begin(); + $new_string = str_repeat("teststring-changed", 500); + file_put_contents($a, $new_string); + + $ret = $storage->gzip_compress(); + $this->t->is($ret, true, "Compression succeeded"); + + $this->t->is(file_exists($file), true, "Test file still exists"); + $this->t->is(file_get_contents($file), 'teststring-old', "Test file still has old content"); + + $storage->commit(); + + ob_start(); + readgzfile($file); + $file_content = ob_get_clean(); + + $this->t->is_deeply($new_string, $file_content, "File is compressed and has correct content"); + + unlink($file); + } + + public function test_unlink() + { + $file = $this->tempdir.'/testfile-unlink'; + file_put_contents($file, "teststring-old"); + + $storage = new \service\storage($file); + $this->t->is(file_exists($file), true, "Test file exists"); + $storage->unlink(); + $this->t->is(file_exists($file), false, "Test file has been removed"); + } + + public function test_unlink_missingFile() + { + $file = $this->tempdir.'/testfile-unlink'; + + $storage = new \service\storage($file); + $this->t->is(file_exists($file), false, "Test file does nto exist"); + $storage->unlink(); + $this->t->is(file_exists($file), false, "Test file still doesn't exist"); + } + + public function test_begin_calledMultipleTimes() + { + $file = $this->tempdir.'/testfile-begin-multi'; + file_put_contents($file, "teststring-old"); + + $storage = new \service\storage($file); + $a = $storage->begin(); + file_put_contents($a, "blub"); + + $b = $storage->begin(); + file_put_contents($b, "second write"); + + $storage->commit(); + $this->t->is(file_get_contents($file), "second write", "File contains second write"); + + unlink($file); + } + + public function test_begin_creationOfDir() + { + $dir = $this->tempdir.'/testdir/'; + $file = $dir.'testfile'; + $storage = new \service\storage($file); + + $this->t->is(is_dir($dir), false, "Directory does not exist"); + + $a = $storage->begin(); + + $this->t->is(is_dir($dir), true, "Directory exists"); + + $storage->rollback(); + rmdir($dir); + } + +} + diff --git a/application/test/tests/test_service_user.php b/application/test/tests/test_service_user.php new file mode 100644 index 000000000..d7e34a71b --- /dev/null +++ b/application/test/tests/test_service_user.php @@ -0,0 +1,65 @@ +<?php +/* + * Copyright 2018 Florian "Bluewind" Pritz <bluewind@server-speed.net> + * + * Licensed under AGPLv3 + * (see COPYING for full license text) + * + */ + +namespace test\tests; + +class test_service_user extends \test\Test { + + public function __construct() { + parent::__construct(); + } + + public function init() { + } + + public function cleanup() { + } + + public function test_invitation_key_delete() { + $CI =& get_instance(); + + $userid = 1; + + $result = $CI->db->select('user, key, action')->from('actions')->get()->result_array(); + $this->t->is_deeply([], $result, "database contains no actions"); + + $key = \service\user::create_invitation_key($userid); + + $result = $CI->db->select('user, key, action')->from('actions')->get()->result_array(); + $this->t->is_deeply([['user' => "".$userid, 'key' => $key, 'action' => 'invitation']], $result, "database contains new key"); + + $ret = \service\user::delete_invitation_key($userid+1, $key); + $this->t->is(0, $ret, "Should have removed no keys because incorrect user/key"); + $result = $CI->db->select('user, key, action')->from('actions')->get()->result_array(); + $this->t->is_deeply([['user' => "".$userid, 'key' => $key, 'action' => 'invitation']], $result, "database contains new key after incorrect deletion"); + + $ret = \service\user::delete_invitation_key($userid+1, "foobar-"); + $this->t->is(0, $ret, "Should have removed no keys because incorrect user/key"); + $result = $CI->db->select('user, key, action')->from('actions')->get()->result_array(); + $this->t->is_deeply([['user' => "".$userid, 'key' => $key, 'action' => 'invitation']], $result, "database contains new key after incorrect deletion"); + + $ret = \service\user::delete_invitation_key($userid+1, ""); + $this->t->is(0, $ret, "Should have removed no keys because incorrect user/key"); + $result = $CI->db->select('user, key, action')->from('actions')->get()->result_array(); + $this->t->is_deeply([['user' => "".$userid, 'key' => $key, 'action' => 'invitation']], $result, "database contains new key after incorrect deletion"); + + $ret = \service\user::delete_invitation_key($userid, ""); + $this->t->is(0, $ret, "Should have removed no keys because incorrect user/key"); + $result = $CI->db->select('user, key, action')->from('actions')->get()->result_array(); + $this->t->is_deeply([['user' => "".$userid, 'key' => $key, 'action' => 'invitation']], $result, "database contains new key"); + + $ret = \service\user::delete_invitation_key($userid, $key); + $this->t->is(1, $ret, "One key should be removed"); + $result = $CI->db->select('user, key, action')->from('actions')->get()->result_array(); + $this->t->is_deeply([], $result, "key has been deleted"); + + } + +} + |