summaryrefslogtreecommitdiffstats
path: root/application/test
diff options
context:
space:
mode:
Diffstat (limited to 'application/test')
-rw-r--r--application/test/Test.php186
-rw-r--r--application/test/tests/api_v2/common.php60
-rw-r--r--application/test/tests/api_v2/test_api_permissions.php108
-rw-r--r--application/test/tests/api_v2/test_create_apikey.php66
-rw-r--r--application/test/tests/api_v2/test_file_create_multipaste.php148
-rw-r--r--application/test/tests/api_v2/test_file_delete.php82
-rw-r--r--application/test/tests/api_v2/test_file_upload.php112
-rw-r--r--application/test/tests/api_v2/test_history.php137
-rw-r--r--application/test/tests/api_v2/test_misc.php48
-rw-r--r--application/test/tests/api_v2/test_user_delete_apikey.php49
-rw-r--r--application/test/tests/test_database_schema.php38
-rw-r--r--application/test/tests/test_filebin_helper.php108
-rw-r--r--application/test/tests/test_libraries_exif.php48
-rw-r--r--application/test/tests/test_libraries_image.php110
-rw-r--r--application/test/tests/test_libraries_output_cache.php82
-rw-r--r--application/test/tests/test_libraries_procrunner.php122
-rw-r--r--application/test/tests/test_libraries_pygments.php115
-rw-r--r--application/test/tests/test_libraries_tempfile.php46
-rw-r--r--application/test/tests/test_models_muser.php113
-rw-r--r--application/test/tests/test_service_files.php121
-rw-r--r--application/test/tests/test_service_files_valid_id.php115
-rw-r--r--application/test/tests/test_service_multipaste_queue.php93
-rw-r--r--application/test/tests/test_service_storage.php172
-rw-r--r--application/test/tests/test_service_user.php65
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");
+
+ }
+
+}
+