diff --git a/meson.build b/meson.build index f5c4f3f..b86268c 100644 --- a/meson.build +++ b/meson.build @@ -124,8 +124,10 @@ alltests = executable('alltests', 'test/webfuse/jsonrpc/test_response_parser.cc', 'test/webfuse/timer/test_timepoint.cc', 'test/webfuse/timer/test_timer.cc', + 'test/webfuse/test_util/mountpoint_factory.cc', 'test/webfuse/test_util/tempdir.cc', 'test/webfuse/test_util/server.cc', + 'test/webfuse/test_util/server_protocol.cc', 'test/webfuse/test_util/ws_server.cc', 'test/webfuse/test_util/ws_client.cc', 'test/webfuse/test_util/adapter_client.cc', @@ -146,6 +148,7 @@ alltests = executable('alltests', 'test/webfuse/test_message.cc', 'test/webfuse/test_message_queue.cc', 'test/webfuse/test_server.cc', + 'test/webfuse/test_server_protocol.cc', 'test/webfuse/test_server_config.cc', 'test/webfuse/test_credentials.cc', 'test/webfuse/test_authenticator.cc', diff --git a/test/webfuse/test_server.cc b/test/webfuse/test_server.cc index cc65824..bc4bee5 100644 --- a/test/webfuse/test_server.cc +++ b/test/webfuse/test_server.cc @@ -28,8 +28,6 @@ using testing::AnyNumber; using testing::AtMost; using testing::Return; -#define TIMEOUT (std::chrono::seconds(10)) - namespace { struct wf_mountpoint * @@ -78,7 +76,7 @@ TEST(server, add_filesystem) Server server; MockInvokationHander handler; EXPECT_CALL(handler, Invoke(StrEq("lookup"), _)).Times(AnyNumber()); - EXPECT_CALL(handler, Invoke(StrEq("getattr"), GetAttr(1))).Times(1) + EXPECT_CALL(handler, Invoke(StrEq("getattr"), GetAttr(1))).Times(AnyNumber()) .WillOnce(Return("{\"mode\": 420, \"type\": \"dir\"}")); WsClient client(handler, WF_PROTOCOL_NAME_PROVIDER_CLIENT); @@ -95,6 +93,7 @@ TEST(server, add_filesystem) json_decref(response); std::string base_dir = server.GetBaseDir(); + ASSERT_TRUE(File(base_dir).isDirectory()); File file(base_dir + "/test"); ASSERT_TRUE(file.isDirectory()); @@ -155,7 +154,7 @@ TEST(server, add_filesystem_fail_invalid_name) auto connected = client.Connect(server.GetPort(), WF_PROTOCOL_NAME_ADAPTER_SERVER); ASSERT_TRUE(connected); - std::string response_text = client.Invoke("{\"method\": \"add_filesystem\", \"params\": [\"invalid/name\"], \"id\": 42}"); + std::string response_text = client.Invoke("{\"method\": \"add_filesystem\", \"params\": [\"invalid_1/name\"], \"id\": 42}"); json_t * response = json_loads(response_text.c_str(), 0, nullptr); ASSERT_TRUE(json_is_object(response)); json_t * error = json_object_get(response, "error"); diff --git a/test/webfuse/test_server_protocol.cc b/test/webfuse/test_server_protocol.cc new file mode 100644 index 0000000..bc608ba --- /dev/null +++ b/test/webfuse/test_server_protocol.cc @@ -0,0 +1,117 @@ +#include "webfuse/test_util/server_protocol.hpp" +#include "webfuse/test_util/ws_client.hpp" +#include "webfuse/test_util/file.hpp" +#include "webfuse/mocks/mock_invokation_handler.hpp" +#include "webfuse/mocks/getattr_matcher.hpp" +#include "webfuse/protocol_names.h" + +#include +#include + +using webfuse_test::MockInvokationHander; +using webfuse_test::WsClient; +using webfuse_test::ServerProtocol; +using webfuse_test::File; +using webfuse_test::GetAttr; + +using testing::_; +using testing::AnyNumber; +using testing::StrEq; +using testing::Return; + +TEST(server_protocol, add_filesystem) +{ + ServerProtocol server; + MockInvokationHander handler; + EXPECT_CALL(handler, Invoke(StrEq("lookup"), _)).Times(AnyNumber()); + EXPECT_CALL(handler, Invoke(StrEq("getattr"), GetAttr(1))).Times(AnyNumber()) + .WillOnce(Return("{\"mode\": 420, \"type\": \"dir\"}")); + WsClient client(handler, WF_PROTOCOL_NAME_PROVIDER_CLIENT); + + auto connected = client.Connect(server.GetPort(), WF_PROTOCOL_NAME_ADAPTER_SERVER, false); + ASSERT_TRUE(connected); + + { + std::string response_text = client.Invoke("{\"method\": \"authenticate\", \"params\": [\"username\", {\"username\": \"bob\", \"password\": \"secret\"}], \"id\": 23}"); + json_t * response = json_loads(response_text.c_str(), 0, nullptr); + ASSERT_TRUE(json_is_object(response)); + json_t * result = json_object_get(response, "result"); + ASSERT_TRUE(json_is_object(result)); + json_t * id = json_object_get(response, "id"); + ASSERT_EQ(23, json_integer_value(id)); + json_decref(response); + } + + { + std::string response_text = client.Invoke("{\"method\": \"add_filesystem\", \"params\": [\"test\"], \"id\": 42}"); + json_t * response = json_loads(response_text.c_str(), 0, nullptr); + ASSERT_TRUE(json_is_object(response)); + json_t * result = json_object_get(response, "result"); + ASSERT_TRUE(json_is_object(result)); + json_t * id = json_object_get(response, "id"); + ASSERT_EQ(42, json_integer_value(id)); + json_decref(response); + } + + std::string base_dir = server.GetBaseDir(); + ASSERT_TRUE(File(base_dir).isDirectory()); + File file(base_dir + "/test"); + ASSERT_TRUE(file.isDirectory()); + + auto disconnected = client.Disconnect(); + ASSERT_TRUE(disconnected); +} + +TEST(server_protocol, add_filesystem_fail_without_authentication) +{ + ServerProtocol server; + MockInvokationHander handler; + EXPECT_CALL(handler, Invoke(StrEq("lookup"), _)).Times(AnyNumber()); + EXPECT_CALL(handler, Invoke(StrEq("getattr"), GetAttr(1))).Times(AnyNumber()) + .WillOnce(Return("{\"mode\": 420, \"type\": \"dir\"}")); + WsClient client(handler, WF_PROTOCOL_NAME_PROVIDER_CLIENT); + + auto connected = client.Connect(server.GetPort(), WF_PROTOCOL_NAME_ADAPTER_SERVER, false); + ASSERT_TRUE(connected); + + { + std::string response_text = client.Invoke("{\"method\": \"add_filesystem\", \"params\": [\"test\"], \"id\": 42}"); + json_t * response = json_loads(response_text.c_str(), 0, nullptr); + ASSERT_TRUE(json_is_object(response)); + json_t * error = json_object_get(response, "error"); + ASSERT_TRUE(json_is_object(error)); + json_t * id = json_object_get(response, "id"); + ASSERT_EQ(42, json_integer_value(id)); + json_decref(response); + } + + auto disconnected = client.Disconnect(); + ASSERT_TRUE(disconnected); +} + +TEST(server_protocol, authenticate_fail_wrong_credentials) +{ + ServerProtocol server; + MockInvokationHander handler; + EXPECT_CALL(handler, Invoke(StrEq("lookup"), _)).Times(AnyNumber()); + EXPECT_CALL(handler, Invoke(StrEq("getattr"), GetAttr(1))).Times(AnyNumber()) + .WillOnce(Return("{\"mode\": 420, \"type\": \"dir\"}")); + WsClient client(handler, WF_PROTOCOL_NAME_PROVIDER_CLIENT); + + auto connected = client.Connect(server.GetPort(), WF_PROTOCOL_NAME_ADAPTER_SERVER, false); + ASSERT_TRUE(connected); + + { + std::string response_text = client.Invoke("{\"method\": \"authenticate\", \"params\": [\"username\", {\"username\": \"alice\", \"password\": \"cheshire\"}], \"id\": 23}"); + json_t * response = json_loads(response_text.c_str(), 0, nullptr); + ASSERT_TRUE(json_is_object(response)); + json_t * error = json_object_get(response, "error"); + ASSERT_TRUE(json_is_object(error)); + json_t * id = json_object_get(response, "id"); + ASSERT_EQ(23, json_integer_value(id)); + json_decref(response); + } + + auto disconnected = client.Disconnect(); + ASSERT_TRUE(disconnected); +} diff --git a/test/webfuse/test_util/mountpoint_factory.cc b/test/webfuse/test_util/mountpoint_factory.cc new file mode 100644 index 0000000..267fd20 --- /dev/null +++ b/test/webfuse/test_util/mountpoint_factory.cc @@ -0,0 +1,41 @@ +#include "webfuse/test_util/mountpoint_factory.hpp" +#include "webfuse/mountpoint.h" + +#include +#include +#include +#include +#include + +extern "C" +{ + +static void +webfuse_test_cleanup_mountpoint( + void * user_data) +{ + char * path = reinterpret_cast(user_data); + rmdir(path); + free(path); +} + +struct wf_mountpoint * +webfuse_test_create_mountpoint( + char const * filesystem, + void * user_data) +{ + char const * base_dir = reinterpret_cast(user_data); + char * path; + asprintf(&path, "%s/%s", base_dir, filesystem); + mkdir(path, 0755); + struct wf_mountpoint * mountpoint = wf_mountpoint_create(path); + wf_mountpoint_set_userdata( + mountpoint, + reinterpret_cast(path), + &webfuse_test_cleanup_mountpoint); + + return mountpoint; +} + + +} \ No newline at end of file diff --git a/test/webfuse/test_util/mountpoint_factory.hpp b/test/webfuse/test_util/mountpoint_factory.hpp new file mode 100644 index 0000000..f66e902 --- /dev/null +++ b/test/webfuse/test_util/mountpoint_factory.hpp @@ -0,0 +1,17 @@ +#ifndef WF_TEST_UTIL_MOUNTPOINT_FACTORY_HPP +#define WF_TEST_UTIL_MOUNTPOINT_FACTORY_HPP + +extern "C" +{ + +struct wf_mountpoint; + +extern wf_mountpoint * +webfuse_test_create_mountpoint( + char const * filesystem, + void * user_data); + + +} + +#endif diff --git a/test/webfuse/test_util/server.cc b/test/webfuse/test_util/server.cc index 46d7bd3..f2725c7 100644 --- a/test/webfuse/test_util/server.cc +++ b/test/webfuse/test_util/server.cc @@ -1,48 +1,12 @@ #include "webfuse/test_util/server.hpp" + +#include "webfuse/test_util/mountpoint_factory.hpp" +#include "webfuse/test_util/tempdir.hpp" +#include "webfuse/impl/server.h" +#include "webfuse/webfuse.h" + #include #include -#include -#include -#include -#include -#include - -#include "webfuse/webfuse.h" -#include "webfuse/impl/server.h" -#include "webfuse/test_util/tempdir.hpp" - -#define WF_PATH_MAX (100) - -extern "C" -{ - -static void webfuse_test_server_cleanup_mountpoint( - void * user_data) -{ - char * path = reinterpret_cast(user_data); - rmdir(path); - free(path); -} - -static struct wf_mountpoint * -webfuse_test_server_create_mountpoint( - char const * filesystem, - void * user_data) -{ - char const * base_dir = reinterpret_cast(user_data); - char path[WF_PATH_MAX]; - snprintf(path, WF_PATH_MAX, "%s/%s", base_dir, filesystem); - mkdir(path, 0755); - struct wf_mountpoint * mountpoint = wf_mountpoint_create(path); - wf_mountpoint_set_userdata( - mountpoint, - reinterpret_cast(strdup(path)), - &webfuse_test_server_cleanup_mountpoint); - - return mountpoint; -} - -} namespace webfuse_test { @@ -52,12 +16,12 @@ class Server::Private public: Private() : is_shutdown_requested(false) - , tempdir("webfuse_test_integration") + , tempdir("webfuse_test_server") { config = wf_server_config_create(); wf_server_config_set_port(config, 0); wf_server_config_set_mountpoint_factory(config, - &webfuse_test_server_create_mountpoint, + &webfuse_test_create_mountpoint, reinterpret_cast(const_cast(tempdir.path()))); wf_server_config_set_keypath(config, "server-key.pem"); wf_server_config_set_certpath(config, "server-cert.pem"); diff --git a/test/webfuse/test_util/server_protocol.cc b/test/webfuse/test_util/server_protocol.cc new file mode 100644 index 0000000..0f2a655 --- /dev/null +++ b/test/webfuse/test_util/server_protocol.cc @@ -0,0 +1,133 @@ +#include "webfuse/test_util/server_protocol.hpp" +#include "webfuse/test_util/mountpoint_factory.hpp" +#include "webfuse/test_util/tempdir.hpp" +#include "webfuse/server_protocol.h" +#include "webfuse/credentials.h" + +#include + +#include +#include +#include + +namespace webfuse_test +{ + +extern "C" bool webfuse_test_server_protocol_authenticate( + wf_credentials const * credentials, + void *) +{ + char const * username = wf_credentials_get(credentials, "username"); + char const * password = wf_credentials_get(credentials, "password"); + + return ((nullptr != username) && (nullptr != password) && + (0 == strcmp("bob", username)) && + (0 == strcmp("secret", password))); +} + + +class ServerProtocol::Private +{ +public: + Private(); + ~Private(); + char const * GetBaseDir(); + int GetPort() const; +private: + static void Run(Private * self); + bool is_shutdown_requested; + TempDir tempdir; + int port; + wf_server_protocol * protocol; + lws_context * context; + lws_context_creation_info info; + lws_protocols protocols[2]; + std::thread thread; + std::mutex mutex; +}; + +ServerProtocol::ServerProtocol() +: d(new Private()) +{ + +} + +ServerProtocol::~ServerProtocol() +{ + delete d; +} + +char const * ServerProtocol::GetBaseDir() const +{ + return d->GetBaseDir(); +} + +int ServerProtocol::GetPort() const +{ + return d->GetPort(); +} + +ServerProtocol::Private::Private() +: is_shutdown_requested(false) +, tempdir("webfuse_test_server_protocol") +{ + protocol = wf_server_protocol_create( + &webfuse_test_create_mountpoint, + reinterpret_cast(const_cast(tempdir.path()))); + + wf_server_protocol_add_authenticator(protocol, "username", + &webfuse_test_server_protocol_authenticate, nullptr); + + memset(protocols, 0, 2 * sizeof(lws_protocols)); + wf_server_protocol_init_lws(protocol, &protocols[0]); + + memset(&info, 0, sizeof(lws_context_creation_info)); + info.port = 0; + info.protocols = protocols; + info.vhost_name = "localhost"; + info.ws_ping_pong_interval = 10; + info.options = LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE; + info.options |= LWS_SERVER_OPTION_EXPLICIT_VHOSTS; + + context = lws_create_context(&info); + + struct lws_vhost * const vhost = lws_create_vhost(context, &info); + port = lws_get_vhost_port(vhost); + + thread = std::thread(&Run, this); +} + +ServerProtocol::Private::~Private() +{ + std::unique_lock lock(mutex); + is_shutdown_requested = true; + lock.unlock(); + + lws_cancel_service(context); + thread.join(); + lws_context_destroy(context); + wf_server_protocol_dispose(protocol); +} + +char const * ServerProtocol::Private::GetBaseDir() +{ + return tempdir.path(); +} + +int ServerProtocol::Private::GetPort() const +{ + return port; +} + +void ServerProtocol::Private::Run(Private * self) +{ + std::unique_lock lock(self->mutex); + while(!self->is_shutdown_requested) + { + lock.unlock(); + lws_service(self->context, 0); + lock.lock(); + } +} + +} \ No newline at end of file diff --git a/test/webfuse/test_util/server_protocol.hpp b/test/webfuse/test_util/server_protocol.hpp new file mode 100644 index 0000000..70175a6 --- /dev/null +++ b/test/webfuse/test_util/server_protocol.hpp @@ -0,0 +1,22 @@ +#ifndef WF_TEST_UTIL_SERVER_PROTOCOL_HPP +#define WF_TEST_UTIL_SERVER_PROTOCOL_HPP + +namespace webfuse_test +{ + +class ServerProtocol +{ +public: + ServerProtocol(); + ~ServerProtocol(); + char const * GetBaseDir() const; + int GetPort() const; +private: + class Private; + Private * d; +}; + +} + + +#endif