From a6cc5ff0c6c22697f1902929bf11a14c2fac2cec Mon Sep 17 00:00:00 2001 From: mmd-osm Date: Thu, 30 Nov 2023 22:46:57 +0100 Subject: [PATCH 1/2] Migrate test_apidb_backend_oauth2 to Catch2 --- src/Makefile.am | 1 + test/test_apidb_backend_oauth2.cpp | 452 +++++++++++++---------------- test/test_database.cpp | 25 +- test/test_database.hpp | 7 +- 4 files changed, 225 insertions(+), 260 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index 7df28dd1..ad116b59 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -138,6 +138,7 @@ ___test_test_apidb_backend_oauth_SOURCES=\ ../test/test_formatter.cpp \ ../test/test_database.cpp \ ../test/test_request.cpp +___test_test_apidb_backend_oauth2_CPPFLAGS=$(AM_CPPFLAGS) $(CATCH2_CPPFLAGS) ___test_test_apidb_backend_oauth2_SOURCES=\ ../test/test_apidb_backend_oauth2.cpp \ ../test/test_formatter.cpp \ diff --git a/test/test_apidb_backend_oauth2.cpp b/test/test_apidb_backend_oauth2.cpp index e76cdd31..17dcbb4b 100644 --- a/test/test_apidb_backend_oauth2.cpp +++ b/test/test_apidb_backend_oauth2.cpp @@ -9,8 +9,6 @@ #include #include -#include -#include #include #include @@ -26,164 +24,12 @@ #include "test_database.hpp" #include "test_request.hpp" -namespace { - -template -std::ostream& operator<<(std::ostream& os, std::optional const& opt) -{ - return opt ? os << opt.value() : os; -} - -} - -template <> struct fmt::formatter> { - template - auto format(const std::optional& v, FormatContext& ctx) -> decltype(ctx.out()) { - // ctx.out() is an output iterator to write to. - std::ostringstream ostr; - ostr << v; - return format_to(ctx.out(), "{}", ostr.str()); - } - constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); } -}; +#define CATCH_CONFIG_MAIN +#include namespace { -template -void assert_equal(const T& a, const T&b, const std::string &message) { - if (a != b) { - throw std::runtime_error(fmt::format("Expecting {} to be equal, but {} != {}", message, a, b)); - } -} - - - -void test_user_id_for_oauth2_token(test_database &tdb) { - tdb.run_sql(R"( - INSERT INTO users (id, email, pass_crypt, creation_time, display_name, data_public) - VALUES - (1, 'user_1@example.com', '', '2013-11-14T02:10:00Z', 'user_1', true), - (2, 'user_2@example.com', '', '2021-03-12T01:33:43Z', 'user_2', true); - - INSERT INTO oauth_applications (id, owner_type, owner_id, name, uid, secret, redirect_uri, scopes, confidential, created_at, updated_at) - VALUES (3, 'User', 1, 'App 1', 'dHKmvGkmuoMjqhCNmTJkf-EcnA61Up34O1vOHwTSvU8', '965136b8fb8d00e2faa2faaaed99c0ec10225518d0c8d9fb1d2af701e87eb68c', - 'http://demo.localhost:3000', 'write_api read_gpx', false, '2021-04-12 17:53:30', '2021-04-12 17:53:30'); - - INSERT INTO oauth_applications (id, owner_type, owner_id, name, uid, secret, redirect_uri, scopes, confidential, created_at, updated_at) - VALUES (4, 'User', 2, 'App 2', 'WNr9KjjzA9uNCXXBHG1AReR2jdottwlKYOz7CLgjUAk', 'cdd6f17bc32eb96b33839db59ae5873777e95864cd936ae445f2dedec8787212', - 'http://localhost:3000/demo', 'write_prefs write_diary', true, '2021-04-13 18:59:11', '2021-04-13 18:59:11'); - - INSERT INTO public.oauth_access_tokens (id, resource_owner_id, application_id, token, refresh_token, expires_in, revoked_at, created_at, scopes, previous_refresh_token) - VALUES (67, 1, 3, '4f41f2328befed5a33bcabdf14483081c8df996cbafc41e313417776e8fafae8', NULL, NULL, NULL, '2021-04-14 19:38:21', 'write_api', ''); - - INSERT INTO public.oauth_access_tokens (id, resource_owner_id, application_id, token, refresh_token, expires_in, revoked_at, created_at, scopes, previous_refresh_token) - VALUES (68, 1, 3, '1187c28b93ab4a14e3df6a61ef46a24d7d4d7964c1d56eb2bfd197b059798c1d', NULL, NULL, '2021-04-15 06:11:01', '2021-04-14 22:06:58', 'write_api', ''); - - INSERT INTO public.oauth_access_tokens (id, resource_owner_id, application_id, token, refresh_token, expires_in, revoked_at, created_at, scopes, previous_refresh_token) - VALUES (69, 1, 3, '9d3e411efa288369a509d8798d17b2a669f331452cdd5d86cd696dad46517e6d', NULL, NULL, NULL, '2021-04-14 19:38:21', 'read_prefs write_api', ''); - - INSERT INTO public.oauth_access_tokens (id, resource_owner_id, application_id, token, refresh_token, expires_in, revoked_at, created_at, scopes, previous_refresh_token) - VALUES (70, 1, 3, 'e466d2ba2ff5da35fdaa7547eb6c27ae0461c7a4acc05476c0a33b1b1d0788cd', NULL, NULL, NULL, '2021-04-14 19:38:21', 'read_prefs read_gpx', ''); - - INSERT INTO public.oauth_access_tokens (id, resource_owner_id, application_id, token, refresh_token, expires_in, revoked_at, created_at, scopes, previous_refresh_token) - VALUES (71, 1, 3, 'f0e6f310ee3a9362fe00cee4328ad318a1fa6c770b2e19975271da99a6407476', NULL, 3600, NULL, now() at time zone 'utc' - '2 hours' :: interval, 'write_api', ''); - - INSERT INTO public.oauth_access_tokens (id, resource_owner_id, application_id, token, refresh_token, expires_in, revoked_at, created_at, scopes, previous_refresh_token) - VALUES (72, 1, 3, 'b1294a183bf64f4d9a97f24ed84ce88e3ab6e7ada78114d6e600bdb63831237b', NULL, 3600, NULL, now() at time zone 'utc' - '30 minutes' :: interval, 'write_api', ''); - - - )"); - - auto store = tdb.get_oauth_store(); - - // Note: Tokens in this unit tests are considered to be opaque strings, tokens are used for db lookups as-is. - // It doesn't matter if they have been previously stored as plain or sha256-hashed tokens. - // Also see test_oauth2.cpp for oauth2::validate_bearer_token tests, which include auth token hash calculation - - // Valid token w/ write API scope - { - bool allow_api_write; - bool expired; - bool revoked; - const auto user_id = store->get_user_id_for_oauth2_token("4f41f2328befed5a33bcabdf14483081c8df996cbafc41e313417776e8fafae8", expired, revoked, allow_api_write); - assert_equal>(user_id, 1, "test_apidb_backend_oauth2::001 - user id"); - assert_equal(allow_api_write, true, "test_apidb_backend_oauth2::001 - allow_api_write"); - assert_equal(expired, false, "test_apidb_backend_oauth2::001 - expired"); - assert_equal(revoked, false, "test_apidb_backend_oauth2::001 - revoked"); - } - - // Invalid (non existing) token - { - bool allow_api_write; - bool expired; - bool revoked; - const auto user_id = store->get_user_id_for_oauth2_token("a6ee343e3417915c87f492aac2a7b638647ef576e2a03256bbf1854c7e06c163", expired, revoked, allow_api_write); - assert_equal(user_id.has_value(), false, "test_apidb_backend_oauth2::002"); - } - - // Revoked token - { - bool allow_api_write; - bool expired; - bool revoked; - const auto user_id = store->get_user_id_for_oauth2_token("1187c28b93ab4a14e3df6a61ef46a24d7d4d7964c1d56eb2bfd197b059798c1d", expired, revoked, allow_api_write); - assert_equal>(user_id, 1, "test_apidb_backend_oauth2::003 - user id"); - assert_equal(allow_api_write, true, "test_apidb_backend_oauth2::003 - allow_api_write"); - assert_equal(expired, false, "test_apidb_backend_oauth2::003 - expired"); - assert_equal(revoked, true, "test_apidb_backend_oauth2::003 - revoked"); - } - - // Two scopes, including write_api - { - bool allow_api_write; - bool expired; - bool revoked; - const auto user_id = store->get_user_id_for_oauth2_token("4f41f2328befed5a33bcabdf14483081c8df996cbafc41e313417776e8fafae8", expired, revoked, allow_api_write); - assert_equal>(user_id, 1, "test_apidb_backend_oauth2::004 - user id"); - assert_equal(allow_api_write, true, "test_apidb_backend_oauth2::004 - allow_api_write"); - assert_equal(expired, false, "test_apidb_backend_oauth2::004 - expired"); - assert_equal(revoked, false, "test_apidb_backend_oauth2::004 - revoked"); - } - - // Two scopes, not write_api - { - bool allow_api_write; - bool expired; - bool revoked; - const auto user_id = store->get_user_id_for_oauth2_token("e466d2ba2ff5da35fdaa7547eb6c27ae0461c7a4acc05476c0a33b1b1d0788cd", expired, revoked, allow_api_write); - assert_equal>(user_id, 1, "test_apidb_backend_oauth2::005 - user id"); - assert_equal(allow_api_write, false, "test_apidb_backend_oauth2::005 - allow_api_write"); - assert_equal(expired, false, "test_apidb_backend_oauth2::005 - expired"); - assert_equal(revoked, false, "test_apidb_backend_oauth2::005 - revoked"); - } - - // expired token - { - bool allow_api_write; - bool expired; - bool revoked; - const auto user_id = store->get_user_id_for_oauth2_token("f0e6f310ee3a9362fe00cee4328ad318a1fa6c770b2e19975271da99a6407476", expired, revoked, allow_api_write); - assert_equal>(user_id, 1, "test_apidb_backend_oauth2::006 - user id"); - assert_equal(allow_api_write, true, "test_apidb_backend_oauth2::006 - allow_api_write"); - assert_equal(expired, true, "test_apidb_backend_oauth2::006 - expired"); - assert_equal(revoked, false, "test_apidb_backend_oauth2::006 - revoked"); - } - - // token to expire in about 30 minutes - { - bool allow_api_write; - bool expired; - bool revoked; - const auto user_id = store->get_user_id_for_oauth2_token("b1294a183bf64f4d9a97f24ed84ce88e3ab6e7ada78114d6e600bdb63831237b", expired, revoked, allow_api_write); - assert_equal>(user_id, 1, "test_apidb_backend_oauth2::006 - user id"); - assert_equal(allow_api_write, true, "test_apidb_backend_oauth2::007 - allow_api_write"); - assert_equal(expired, false, "test_apidb_backend_oauth2::007 - expired"); - assert_equal(revoked, false, "test_apidb_backend_oauth2::007 - revoked"); - } - -} - class empty_data_selection : public data_selection { public: @@ -266,31 +112,19 @@ struct recording_rate_limiter std::set m_keys_seen; }; - -void create_changeset(test_database &tdb, oauth::store& store, std::string token, int expected_response_code) { - - // Test valid token, create empty changeset - recording_rate_limiter limiter; - std::string generator("test_apidb_backend.cpp"); - routes route; - - auto sel_factory = tdb.get_data_selection_factory(); - auto upd_factory = tdb.get_data_update_factory(); - - test_request req; - req.set_header("SCRIPT_URL", "/api/0.6/changeset/create"); - req.set_header("SCRIPT_URI", - "http://www.openstreetmap.org/api/0.6/changeset/create"); +void add_common_headers(test_request& req) +{ req.set_header("HTTP_HOST", "www.openstreetmap.org"); - req.set_header("HTTP_ACCEPT_ENCODING", - "gzip;q=1.0,deflate;q=0.6,identity;q=0.3"); + req.set_header("HTTP_ACCEPT_ENCODING", "gzip;q=1.0,deflate;q=0.6,identity;q=0.3"); req.set_header("HTTP_ACCEPT", "*/*"); req.set_header("HTTP_USER_AGENT", "OAuth gem v0.4.7"); - req.set_header("HTTP_AUTHORIZATION","Bearer " + token); req.set_header("HTTP_X_REQUEST_ID", "V-eaKX8AAQEAAF4UzHwAAAHt"); req.set_header("HTTP_X_FORWARDED_HOST", "www.openstreetmap.org"); req.set_header("HTTP_X_FORWARDED_SERVER", "www.openstreetmap.org"); req.set_header("HTTP_CONNECTION", "Keep-Alive"); + req.set_header("SERVER_SIGNATURE", "
Apache/2.4.18 (Ubuntu) Server " + "at www.openstreetmap.org Port 80
"); + req.set_header("SERVER_SOFTWARE", "Apache/2.4.18 (Ubuntu)"); req.set_header("SERVER_NAME", "www.openstreetmap.org"); req.set_header("SERVER_ADDR", "127.0.0.1"); req.set_header("SERVER_PORT", "80"); @@ -298,19 +132,37 @@ void create_changeset(test_database &tdb, oauth::store& store, std::string token req.set_header("DOCUMENT_ROOT", "/srv/www.openstreetmap.org/rails/public"); req.set_header("REQUEST_SCHEME", "http"); req.set_header("SERVER_PROTOCOL", "HTTP/1.1"); +} + + +int create_changeset(test_database &tdb, oauth::store& store, std::string token) { + + // Test valid token, create empty changeset + recording_rate_limiter limiter; + std::string generator("test_apidb_backend.cpp"); + routes route; + + auto sel_factory = tdb.get_data_selection_factory(); + auto upd_factory = tdb.get_data_update_factory(); + + test_request req; + req.set_header("SCRIPT_URL", "/api/0.6/changeset/create"); + req.set_header("SCRIPT_URI", "http://www.openstreetmap.org/api/0.6/changeset/create"); + req.set_header("HTTP_AUTHORIZATION","Bearer " + token); req.set_header("REQUEST_METHOD", "PUT"); req.set_header("QUERY_STRING", ""); req.set_header("REQUEST_URI", "/api/0.6/changeset/create"); req.set_header("SCRIPT_NAME", "/api/0.6/changeset/create"); + add_common_headers(req); req.set_payload(R"( )" ); process_request(req, limiter, generator, route, *sel_factory, upd_factory.get(), &store); - assert_equal(expected_response_code, req.response_status(), "response status"); + return (req.response_status()); } -void fetch_relation(test_database &tdb, oauth::store& store, std::string token, int expected_response_code) { +int fetch_relation(test_database &tdb, oauth::store& store, std::string token) { recording_rate_limiter limiter; std::string generator("test_apidb_backend.cpp"); @@ -319,114 +171,214 @@ void fetch_relation(test_database &tdb, oauth::store& store, std::string token, test_request req; req.set_header("SCRIPT_URL", "/api/0.6/relation/165475/full"); - req.set_header("SCRIPT_URI", - "http://www.openstreetmap.org/api/0.6/relation/165475/full"); - req.set_header("HTTP_HOST", "www.openstreetmap.org"); - req.set_header("HTTP_ACCEPT_ENCODING", - "gzip;q=1.0,deflate;q=0.6,identity;q=0.3"); - req.set_header("HTTP_ACCEPT", "*/*"); - req.set_header("HTTP_USER_AGENT", "OAuth gem v0.4.7"); + req.set_header("SCRIPT_URI", "http://www.openstreetmap.org/api/0.6/relation/165475/full"); req.set_header("HTTP_AUTHORIZATION","Bearer " + token); - req.set_header("HTTP_X_REQUEST_ID", "V-eaKX8AAQEAAF4UzHwAAAHt"); - req.set_header("HTTP_X_FORWARDED_HOST", "www.openstreetmap.org"); - req.set_header("HTTP_X_FORWARDED_SERVER", "www.openstreetmap.org"); - req.set_header("HTTP_CONNECTION", "Keep-Alive"); - req.set_header("PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"); - req.set_header("SERVER_SIGNATURE", "
Apache/2.4.18 (Ubuntu) Server " - "at www.openstreetmap.org Port 80
"); - req.set_header("SERVER_SOFTWARE", "Apache/2.4.18 (Ubuntu)"); - req.set_header("SERVER_NAME", "www.openstreetmap.org"); - req.set_header("SERVER_ADDR", "127.0.0.1"); - req.set_header("SERVER_PORT", "80"); - req.set_header("REMOTE_ADDR", "127.0.0.1"); - req.set_header("DOCUMENT_ROOT", "/srv/www.openstreetmap.org/rails/public"); - req.set_header("REQUEST_SCHEME", "http"); - req.set_header("SERVER_PROTOCOL", "HTTP/1.1"); req.set_header("REQUEST_METHOD", "GET"); req.set_header("QUERY_STRING", ""); req.set_header("REQUEST_URI", "/api/0.6/relation/165475/full"); req.set_header("SCRIPT_NAME", "/api/0.6/relation/165475/full"); + add_common_headers(req); process_request(req, limiter, generator, route, *factory, nullptr, &store); - assert_equal(expected_response_code, req.response_status(), "response status"); + return (req.response_status()); } +class DatabaseTestsFixture +{ +protected: + DatabaseTestsFixture() = default; + static test_database tdb; +}; -void test_oauth2_end_to_end(test_database &tdb) { +test_database DatabaseTestsFixture::tdb{}; - // tokens 1yi2RI2W... and 2Kx... are stored in plain text in oauth_access_tokens table, all others as sha256-hash value +struct CGImapListener : Catch::TestEventListenerBase, DatabaseTestsFixture { - // Column status in table users is for information purposes only - // User id 1000 denotes an inactive user (see empty_data_selection, method is_user_active) + using TestEventListenerBase::TestEventListenerBase; // inherit constructor + + void testRunStarting( Catch::TestRunInfo const& testRunInfo ) override { + // load database schema when starting up tests + tdb.setup(); + } + + void testCaseStarting( Catch::TestCaseInfo const& testInfo ) override { + tdb.testcase_starting(); + } - tdb.run_sql(R"( - INSERT INTO users (id, email, pass_crypt, creation_time, display_name, data_public, status) - VALUES - (1, 'user_1@example.com', '', '2013-11-14T02:10:00Z', 'user_1', true, 'confirmed'), - (2, 'user_2@example.com', '', '2021-03-12T01:33:43Z', 'user_2', true, 'active'), - (1000, 'user_1000@example.com', '', '2021-04-12T01:33:43Z', 'user_1000', true, 'pending'); + void testCaseEnded( Catch::TestCaseStats const& testCaseStats ) override { + tdb.testcase_ended(); + } +}; + +CATCH_REGISTER_LISTENER( CGImapListener ) + +} // anonymous namespace - INSERT INTO oauth_applications (id, owner_type, owner_id, name, uid, secret, redirect_uri, scopes, confidential, created_at, updated_at) - VALUES (3, 'User', 1, 'App 1', 'dHKmvGkmuoMjqhCNmTJkf-EcnA61Up34O1vOHwTSvU8', '965136b8fb8d00e2faa2faaaed99c0ec10225518d0c8d9fb1d2af701e87eb68c', - 'http://demo.localhost:3000', 'write_api read_gpx', false, '2021-04-12 17:53:30', '2021-04-12 17:53:30'); - INSERT INTO public.oauth_access_tokens (id, resource_owner_id, application_id, token, refresh_token, expires_in, revoked_at, created_at, scopes, previous_refresh_token) - VALUES (67, 1, 3, '1yi2RI2WhIVMLoLaDLg0nrPJPU4WQSIX4Hh_jxfRRxI', NULL, NULL, NULL, '2021-04-14 19:38:21.991429', 'write_api', ''); - INSERT INTO public.oauth_access_tokens (id, resource_owner_id, application_id, token, refresh_token, expires_in, revoked_at, created_at, scopes, previous_refresh_token) - VALUES (72, 1, 3, '4ea5b956c8882db030a5a799cb45eb933bb6dd2f196a44f68167d96fbc8ec3f1', NULL, NULL, NULL, '2021-04-14 19:38:21.991429', 'read_prefs', ''); +TEST_CASE_METHOD( DatabaseTestsFixture, "test_user_id_for_oauth2_token", "[oauth2][db]" ) { - INSERT INTO public.oauth_access_tokens (id, resource_owner_id, application_id, token, refresh_token, expires_in, revoked_at, created_at, scopes, previous_refresh_token) - VALUES (78, 1000, 3, '2KxONxvhoSji9F8dz_WO6UZOzRdmQ0ISB0ovnZrJnhM', NULL, NULL, NULL, '2021-04-14 19:38:21.991429', 'write_api', ''); - )"); + bool allow_api_write; + bool expired; + bool revoked; auto store = tdb.get_oauth_store(); - // Test valid token -> HTTP 404 not found, due to unknown relation - fetch_relation(tdb, *store, "1yi2RI2WhIVMLoLaDLg0nrPJPU4WQSIX4Hh_jxfRRxI", 404); + SECTION("Initialize test data") { + + tdb.run_sql(R"( + + INSERT INTO users (id, email, pass_crypt, creation_time, display_name, data_public) + VALUES + (1, 'user_1@example.com', '', '2013-11-14T02:10:00Z', 'user_1', true), + (2, 'user_2@example.com', '', '2021-03-12T01:33:43Z', 'user_2', true); + + INSERT INTO oauth_applications (id, owner_type, owner_id, name, uid, secret, redirect_uri, scopes, confidential, created_at, updated_at) + VALUES (3, 'User', 1, 'App 1', 'dHKmvGkmuoMjqhCNmTJkf-EcnA61Up34O1vOHwTSvU8', '965136b8fb8d00e2faa2faaaed99c0ec10225518d0c8d9fb1d2af701e87eb68c', + 'http://demo.localhost:3000', 'write_api read_gpx', false, '2021-04-12 17:53:30', '2021-04-12 17:53:30'); + + INSERT INTO oauth_applications (id, owner_type, owner_id, name, uid, secret, redirect_uri, scopes, confidential, created_at, updated_at) + VALUES (4, 'User', 2, 'App 2', 'WNr9KjjzA9uNCXXBHG1AReR2jdottwlKYOz7CLgjUAk', 'cdd6f17bc32eb96b33839db59ae5873777e95864cd936ae445f2dedec8787212', + 'http://localhost:3000/demo', 'write_prefs write_diary', true, '2021-04-13 18:59:11', '2021-04-13 18:59:11'); + + INSERT INTO public.oauth_access_tokens (id, resource_owner_id, application_id, token, refresh_token, expires_in, revoked_at, created_at, scopes, previous_refresh_token) + VALUES (67, 1, 3, '4f41f2328befed5a33bcabdf14483081c8df996cbafc41e313417776e8fafae8', NULL, NULL, NULL, '2021-04-14 19:38:21', 'write_api', ''); + + INSERT INTO public.oauth_access_tokens (id, resource_owner_id, application_id, token, refresh_token, expires_in, revoked_at, created_at, scopes, previous_refresh_token) + VALUES (68, 1, 3, '1187c28b93ab4a14e3df6a61ef46a24d7d4d7964c1d56eb2bfd197b059798c1d', NULL, NULL, '2021-04-15 06:11:01', '2021-04-14 22:06:58', 'write_api', ''); + + INSERT INTO public.oauth_access_tokens (id, resource_owner_id, application_id, token, refresh_token, expires_in, revoked_at, created_at, scopes, previous_refresh_token) + VALUES (69, 1, 3, '9d3e411efa288369a509d8798d17b2a669f331452cdd5d86cd696dad46517e6d', NULL, NULL, NULL, '2021-04-14 19:38:21', 'read_prefs write_api', ''); + + INSERT INTO public.oauth_access_tokens (id, resource_owner_id, application_id, token, refresh_token, expires_in, revoked_at, created_at, scopes, previous_refresh_token) + VALUES (70, 1, 3, 'e466d2ba2ff5da35fdaa7547eb6c27ae0461c7a4acc05476c0a33b1b1d0788cd', NULL, NULL, NULL, '2021-04-14 19:38:21', 'read_prefs read_gpx', ''); + + INSERT INTO public.oauth_access_tokens (id, resource_owner_id, application_id, token, refresh_token, expires_in, revoked_at, created_at, scopes, previous_refresh_token) + VALUES (71, 1, 3, 'f0e6f310ee3a9362fe00cee4328ad318a1fa6c770b2e19975271da99a6407476', NULL, 3600, NULL, now() at time zone 'utc' - '2 hours' :: interval, 'write_api', ''); + + INSERT INTO public.oauth_access_tokens (id, resource_owner_id, application_id, token, refresh_token, expires_in, revoked_at, created_at, scopes, previous_refresh_token) + VALUES (72, 1, 3, 'b1294a183bf64f4d9a97f24ed84ce88e3ab6e7ada78114d6e600bdb63831237b', NULL, 3600, NULL, now() at time zone 'utc' - '30 minutes' :: interval, 'write_api', ''); + + )"); + } - // Test unknown token -> HTTP 401 Unauthorized - fetch_relation(tdb, *store, "8JrrmoKSUtzBhmenUUQF27PVdQn2QY8YdRfosu3R-Dc", 401); + // Note: Tokens in this unit tests are considered to be opaque strings, tokens are used for db lookups as-is. + // It doesn't matter if they have been previously stored as plain or sha256-hashed tokens. + // Also see test_oauth2.cpp for oauth2::validate_bearer_token tests, which include auth token hash calculation - // Test valid token, create empty changeset + SECTION("Valid token w/ write API scope") { + const auto user_id = store->get_user_id_for_oauth2_token("4f41f2328befed5a33bcabdf14483081c8df996cbafc41e313417776e8fafae8", expired, revoked, allow_api_write); + CHECK(user_id == 1); + REQUIRE(allow_api_write); + REQUIRE_FALSE(expired); + REQUIRE_FALSE(revoked); + } - // missing write_api scope --> http::unauthorized ("You have not granted the modify map permission") - create_changeset(tdb, *store, "hCXrz5B5fCBHusp0EuD2IGwYSxS8bkAnVw2_aLEdxig", 401); + SECTION("Invalid (non existing) token") { + const auto user_id = store->get_user_id_for_oauth2_token("a6ee343e3417915c87f492aac2a7b638647ef576e2a03256bbf1854c7e06c163", expired, revoked, allow_api_write); + REQUIRE_FALSE(user_id.has_value()); + } - // includes write_api scope - create_changeset(tdb, *store, "1yi2RI2WhIVMLoLaDLg0nrPJPU4WQSIX4Hh_jxfRRxI", 200); + SECTION("Revoked token") { + const auto user_id = store->get_user_id_for_oauth2_token("1187c28b93ab4a14e3df6a61ef46a24d7d4d7964c1d56eb2bfd197b059798c1d", expired, revoked, allow_api_write); + CHECK(user_id == 1); + REQUIRE(allow_api_write); + REQUIRE_FALSE(expired); + REQUIRE(revoked); + } - // Same as previous tests case. However, user 1000 is not active this time - // Creating changesets should be rejected with HTTP 403 (Forbidden) - create_changeset(tdb, *store, "2KxONxvhoSji9F8dz_WO6UZOzRdmQ0ISB0ovnZrJnhM", 403); + SECTION("Two scopes, including write_api") { + const auto user_id = store->get_user_id_for_oauth2_token("4f41f2328befed5a33bcabdf14483081c8df996cbafc41e313417776e8fafae8", expired, revoked, allow_api_write); + CHECK(user_id == 1); + REQUIRE(allow_api_write); + REQUIRE_FALSE(expired); + REQUIRE_FALSE(revoked); + } + + SECTION("Two scopes, not write_api") { + const auto user_id = store->get_user_id_for_oauth2_token("e466d2ba2ff5da35fdaa7547eb6c27ae0461c7a4acc05476c0a33b1b1d0788cd", expired, revoked, allow_api_write); + CHECK(user_id == 1); + REQUIRE_FALSE(allow_api_write); + REQUIRE_FALSE(expired); + REQUIRE_FALSE(revoked); + } + + SECTION("expired token") { + const auto user_id = store->get_user_id_for_oauth2_token("f0e6f310ee3a9362fe00cee4328ad318a1fa6c770b2e19975271da99a6407476", expired, revoked, allow_api_write); + CHECK(user_id == 1); + REQUIRE(allow_api_write); + REQUIRE(expired); + REQUIRE_FALSE(revoked); + } + + SECTION("token to expire in about 30 minutes") { + const auto user_id = store->get_user_id_for_oauth2_token("b1294a183bf64f4d9a97f24ed84ce88e3ab6e7ada78114d6e600bdb63831237b", expired, revoked, allow_api_write); + CHECK(user_id == 1); + REQUIRE(allow_api_write); + REQUIRE_FALSE(expired); + REQUIRE_FALSE(revoked); + } } -} // anonymous namespace -int main(int, char **) { - try { - test_database tdb; - tdb.setup(); +TEST_CASE_METHOD( DatabaseTestsFixture, "test_oauth2_end_to_end", "[oauth2][db]" ) { - tdb.run(std::function( - &test_user_id_for_oauth2_token)); + auto store = tdb.get_oauth_store(); - tdb.run(std::function( - &test_oauth2_end_to_end)); + // tokens 1yi2RI2W... and 2Kx... are stored in plain text in oauth_access_tokens table, all others as sha256-hash value + + // Column status in table users is for information purposes only + // User id 1000 denotes an inactive user (see empty_data_selection, method is_user_active) - } catch (const test_database::setup_error &e) { - std::cout << "Unable to set up test database: " << e.what() << std::endl; - return 77; + SECTION("Initialize test data") { + + tdb.run_sql(R"( + + INSERT INTO users (id, email, pass_crypt, creation_time, display_name, data_public, status) + VALUES + (1, 'user_1@example.com', '', '2013-11-14T02:10:00Z', 'user_1', true, 'confirmed'), + (2, 'user_2@example.com', '', '2021-03-12T01:33:43Z', 'user_2', true, 'active'), + (1000, 'user_1000@example.com', '', '2021-04-12T01:33:43Z', 'user_1000', true, 'pending'); + + INSERT INTO oauth_applications (id, owner_type, owner_id, name, uid, secret, redirect_uri, scopes, confidential, created_at, updated_at) + VALUES (3, 'User', 1, 'App 1', 'dHKmvGkmuoMjqhCNmTJkf-EcnA61Up34O1vOHwTSvU8', '965136b8fb8d00e2faa2faaaed99c0ec10225518d0c8d9fb1d2af701e87eb68c', + 'http://demo.localhost:3000', 'write_api read_gpx', false, '2021-04-12 17:53:30', '2021-04-12 17:53:30'); + + INSERT INTO public.oauth_access_tokens (id, resource_owner_id, application_id, token, refresh_token, expires_in, revoked_at, created_at, scopes, previous_refresh_token) + VALUES (67, 1, 3, '1yi2RI2WhIVMLoLaDLg0nrPJPU4WQSIX4Hh_jxfRRxI', NULL, NULL, NULL, '2021-04-14 19:38:21.991429', 'write_api', ''); + + INSERT INTO public.oauth_access_tokens (id, resource_owner_id, application_id, token, refresh_token, expires_in, revoked_at, created_at, scopes, previous_refresh_token) + VALUES (72, 1, 3, '4ea5b956c8882db030a5a799cb45eb933bb6dd2f196a44f68167d96fbc8ec3f1', NULL, NULL, NULL, '2021-04-14 19:38:21.991429', 'read_prefs', ''); + + INSERT INTO public.oauth_access_tokens (id, resource_owner_id, application_id, token, refresh_token, expires_in, revoked_at, created_at, scopes, previous_refresh_token) + VALUES (78, 1000, 3, '2KxONxvhoSji9F8dz_WO6UZOzRdmQ0ISB0ovnZrJnhM', NULL, NULL, NULL, '2021-04-14 19:38:21.991429', 'write_api', ''); + )"); + } - } catch (const std::exception &e) { - std::cout << "Error: " << e.what() << std::endl; - return 1; + SECTION("Fetch relation: test valid token -> HTTP 404 not found, due to unknown relation") { + CHECK(fetch_relation(tdb, *store, "1yi2RI2WhIVMLoLaDLg0nrPJPU4WQSIX4Hh_jxfRRxI") == 404); + } - } catch (...) { - std::cout << "Unknown exception type." << std::endl; - return 99; + SECTION("Fetch relation: test unknown token -> HTTP 401 Unauthorized") { + CHECK(fetch_relation(tdb, *store, "8JrrmoKSUtzBhmenUUQF27PVdQn2QY8YdRfosu3R-Dc") == 401); } - return 0; + // Test valid token, create empty changeset + + // missing write_api scope --> http::unauthorized ("You have not granted the modify map permission") + SECTION("Create empty changeset: missing write_api scope") { + CHECK(create_changeset(tdb, *store, "hCXrz5B5fCBHusp0EuD2IGwYSxS8bkAnVw2_aLEdxig") == 401); + } + + SECTION("Create empty changeset: includes write_api scope") { + CHECK(create_changeset(tdb, *store, "1yi2RI2WhIVMLoLaDLg0nrPJPU4WQSIX4Hh_jxfRRxI") == 200); + } + + // Same as previous tests case. However, user 1000 is not active this time + // Creating changesets should be rejected with HTTP 403 (Forbidden) + SECTION("Create empty changeset: includes write_api scope, user not active") { + CHECK(create_changeset(tdb, *store, "2KxONxvhoSji9F8dz_WO6UZOzRdmQ0ISB0ovnZrJnhM") == 403); + } } + diff --git a/test/test_database.cpp b/test/test_database.cpp index 67d2d2be..0bd968ea 100644 --- a/test/test_database.cpp +++ b/test/test_database.cpp @@ -160,21 +160,31 @@ std::string test_database::random_db_name() { return std::string(name); } +void test_database::testcase_starting() { + + pqxx::connection conn(fmt::format("dbname={}", m_db_name)); + truncate_all_tables(conn); +} + +void test_database::testcase_ended() { + + txn_owner_readonly.reset(); + txn_owner_readwrite.reset(); +} + void test_database::run( std::function func) { try { // clear out database before using it! - pqxx::connection conn(fmt::format("dbname={}", m_db_name)); - truncate_all_tables(conn); + testcase_starting(); func(*this); } catch (const std::exception &e) { throw std::runtime_error(fmt::format("{}", e.what())); } - txn_owner_readonly.reset(); - txn_owner_readwrite.reset(); + testcase_ended(); } void test_database::run_update( @@ -182,16 +192,13 @@ void test_database::run_update( try { // clear out database before using it! - pqxx::connection conn(fmt::format("dbname={}", m_db_name)); - truncate_all_tables(conn); + testcase_starting(); func(*this); } catch (const std::exception &e) { throw std::runtime_error(fmt::format("{}, in update", e.what())); } - - txn_owner_readonly.reset(); - txn_owner_readwrite.reset(); + testcase_ended(); } diff --git a/test/test_database.hpp b/test/test_database.hpp index bfa6c66a..77ec4f1c 100644 --- a/test/test_database.hpp +++ b/test/test_database.hpp @@ -84,6 +84,12 @@ struct test_database { // intended for setting up data that the test needs. int run_sql(const std::string &sql); + // clean up database tables before new test case starts + void testcase_starting(); + + // clean up internal buffers when test case ended + void testcase_ended(); + private: // create a random, and hopefully unique, database name. static std::string random_db_name(); @@ -104,7 +110,6 @@ struct test_database { std::unique_ptr txn_owner_readonly; std::unique_ptr txn_owner_readwrite; - }; #endif /* TEST_TEST_DATABASE_HPP */ From 8f85d02473b45ef0c4a06e7a6bb6025e68c2af94 Mon Sep 17 00:00:00 2001 From: mmd-osm Date: Fri, 1 Dec 2023 22:19:17 +0100 Subject: [PATCH 2/2] Migrate test_apidb_backend_nodes to Catch2 --- src/Makefile.am | 1 + test/test_apidb_backend_nodes.cpp | 362 ++++++++++++++---------------- 2 files changed, 172 insertions(+), 191 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index ad116b59..1313be0a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -128,6 +128,7 @@ ___test_test_parse_options_SOURCES=\ ../test/test_parse_options.cpp if ENABLE_APIDB +___test_test_apidb_backend_nodes_CPPFLAGS=$(AM_CPPFLAGS) $(CATCH2_CPPFLAGS) ___test_test_apidb_backend_nodes_SOURCES=\ ../test/test_apidb_backend_nodes.cpp \ ../test/test_formatter.cpp \ diff --git a/test/test_apidb_backend_nodes.cpp b/test/test_apidb_backend_nodes.cpp index f3ed972d..268568ca 100644 --- a/test/test_apidb_backend_nodes.cpp +++ b/test/test_apidb_backend_nodes.cpp @@ -28,229 +28,209 @@ #include "test_database.hpp" #include "test_request.hpp" +#define CATCH_CONFIG_MAIN +#include + namespace { -template -void assert_equal(const T& a, const T&b, const std::string &message) { - if (a != b) { - throw std::runtime_error(fmt::format("Expecting {} to be equal, but {} != {}", message, a, b)); - } -} +class DatabaseTestsFixture +{ +protected: + DatabaseTestsFixture() = default; + static test_database tdb; +}; -void test_single_nodes(test_database &tdb) { - tdb.run_sql( - "INSERT INTO users (id, email, pass_crypt, creation_time, display_name, data_public) " - "VALUES " - " (1, 'user_1@example.com', '', '2013-11-14T02:10:00Z', 'user_1', true), " - " (2, 'user_2@example.com', '', '2013-11-14T02:10:00Z', 'user_2', false); " - - "INSERT INTO changesets (id, user_id, created_at, closed_at) " - "VALUES " - " (1, 1, '2013-11-14T02:10:00Z', '2013-11-14T03:10:00Z'), " - " (2, 1, '2013-11-14T02:10:00Z', '2013-11-14T03:10:00Z'), " - " (4, 2, '2013-11-14T02:10:00Z', '2013-11-14T03:10:00Z');" - - "INSERT INTO current_nodes (id, latitude, longitude, changeset_id, visible, \"timestamp\", tile, version) " - " VALUES " - " (1, 0, 0, 1, true, '2013-11-14T02:10:00Z', 3221225472, 1), " - " (2, 1000000, 1000000, 1, true, '2013-11-14T02:10:01Z', 3221227032, 1), " - " (3, 0, 0, 2, false, '2015-03-02T18:27:00Z', 3221225472, 2), " - " (4, 0, 0, 4, true, '2015-03-02T19:25:00Z', 3221225472, 1);" - ); - auto sel = tdb.get_data_selection(); +test_database DatabaseTestsFixture::tdb{}; - if (sel->check_node_visibility(1) != data_selection::exists) { - throw std::runtime_error("Node 1 should be visible, but isn't"); - } - if (sel->check_node_visibility(2) != data_selection::exists) { - throw std::runtime_error("Node 2 should be visible, but isn't"); - } +struct CGImapListener : Catch::TestEventListenerBase, DatabaseTestsFixture { - std::vector ids{1,2,3,4}; + using TestEventListenerBase::TestEventListenerBase; // inherit constructor - if (sel->select_nodes(ids) != 4) { - throw std::runtime_error("Selecting 4 nodes failed"); - } - if (sel->select_nodes(ids) != 0) { - throw std::runtime_error("Re-selecting 4 nodes failed"); - } + void testRunStarting( Catch::TestRunInfo const& testRunInfo ) override { + // load database schema when starting up tests + tdb.setup(); + } - assert_equal( - sel->check_node_visibility(1), data_selection::exists, - "node 1 visibility"); - assert_equal( - sel->check_node_visibility(2), data_selection::exists, - "node 2 visibility"); - assert_equal( - sel->check_node_visibility(3), data_selection::deleted, - "node 3 visibility"); - assert_equal( - sel->check_node_visibility(4), data_selection::exists, - "node 4 visibility"); - assert_equal( - sel->check_node_visibility(5), data_selection::non_exist, - "node 5 visibility"); - - test_formatter f; - sel->write_nodes(f); - assert_equal(f.m_nodes.size(), 4, "number of nodes written"); - assert_equal( - test_formatter::node_t( - element_info(1, 1, 1, "2013-11-14T02:10:00Z", 1, std::string("user_1"), true), - 0.0, 0.0, - tags_t() - ), - f.m_nodes[0], "first node written"); - assert_equal( - test_formatter::node_t( - element_info(2, 1, 1, "2013-11-14T02:10:01Z", 1, std::string("user_1"), true), - 0.1, 0.1, - tags_t() - ), - f.m_nodes[1], "second node written"); - assert_equal( - test_formatter::node_t( - element_info(3, 2, 2, "2015-03-02T18:27:00Z", 1, std::string("user_1"), false), - 0.0, 0.0, - tags_t() - ), - f.m_nodes[2], "third node written"); - assert_equal( - test_formatter::node_t( - element_info(4, 1, 4, "2015-03-02T19:25:00Z", {}, {}, true), - 0.0, 0.0, - tags_t() - ), - f.m_nodes[3], "fourth (anonymous) node written"); -} + void testCaseStarting( Catch::TestCaseInfo const& testInfo ) override { + tdb.testcase_starting(); + } -void test_dup_nodes(test_database &tdb) { - tdb.run_sql( - "INSERT INTO users (id, email, pass_crypt, creation_time, display_name, data_public) " - "VALUES " - " (1, 'user_1@example.com', '', '2013-11-14T02:10:00Z', 'user_1', true); " + void testCaseEnded( Catch::TestCaseStats const& testCaseStats ) override { + tdb.testcase_ended(); + } +}; - "INSERT INTO changesets (id, user_id, created_at, closed_at) " - "VALUES " - " (1, 1, '2013-11-14T02:10:00Z', '2013-11-14T03:10:00Z');" +CATCH_REGISTER_LISTENER( CGImapListener ) - "INSERT INTO current_nodes (id, latitude, longitude, changeset_id, visible, \"timestamp\", tile, version) " - " VALUES " - " (1, 0, 0, 1, true, '2013-11-14T02:10:00Z', 3221225472, 1);" - ); +} // anonymous namespace - auto sel = tdb.get_data_selection(); - if (sel->check_node_visibility(1) != data_selection::exists) { - throw std::runtime_error("Node 1 should be visible, but isn't"); - } +TEST_CASE_METHOD( DatabaseTestsFixture, "test_single_nodes", "[nodes][db]" ) { - std::vector ids{1,1,1}; + auto sel = tdb.get_data_selection(); - if (sel->select_nodes(ids) != 1) { - throw std::runtime_error("Selecting 3 duplicates of 1 node failed"); - } - if (sel->select_nodes(ids) != 0) { - throw std::runtime_error("Re-selecting the same node failed"); + SECTION("Initialize test data") { + + tdb.run_sql( + "INSERT INTO users (id, email, pass_crypt, creation_time, display_name, data_public) " + "VALUES " + " (1, 'user_1@example.com', '', '2013-11-14T02:10:00Z', 'user_1', true), " + " (2, 'user_2@example.com', '', '2013-11-14T02:10:00Z', 'user_2', false); " + + "INSERT INTO changesets (id, user_id, created_at, closed_at) " + "VALUES " + " (1, 1, '2013-11-14T02:10:00Z', '2013-11-14T03:10:00Z'), " + " (2, 1, '2013-11-14T02:10:00Z', '2013-11-14T03:10:00Z'), " + " (4, 2, '2013-11-14T02:10:00Z', '2013-11-14T03:10:00Z');" + + "INSERT INTO current_nodes (id, latitude, longitude, changeset_id, visible, \"timestamp\", tile, version) " + " VALUES " + " (1, 0, 0, 1, true, '2013-11-14T02:10:00Z', 3221225472, 1), " + " (2, 1000000, 1000000, 1, true, '2013-11-14T02:10:01Z', 3221227032, 1), " + " (3, 0, 0, 2, false, '2015-03-02T18:27:00Z', 3221225472, 2), " + " (4, 0, 0, 4, true, '2015-03-02T19:25:00Z', 3221225472, 1);" + ); } - assert_equal( - sel->check_node_visibility(1), data_selection::exists, - "node 1 visibility"); - - test_formatter f; - sel->write_nodes(f); - assert_equal(f.m_nodes.size(), 1, "number of nodes written"); - assert_equal( - test_formatter::node_t( - element_info(1, 1, 1, "2013-11-14T02:10:00Z", 1, std::string("user_1"), true), - 0.0, 0.0, - tags_t() - ), - f.m_nodes[0], "first node written"); + SECTION("Perform single node checks") { + + std::vector ids{1,2,3,4}; + + REQUIRE (sel->select_nodes(ids) == 4); // 4 ids read from database + REQUIRE (sel->select_nodes(ids) == 0); // Re-selecting 4 nodes failed, values should be still in buffer + + REQUIRE (sel->check_node_visibility(1) == data_selection::exists); + REQUIRE (sel->check_node_visibility(2) == data_selection::exists); + REQUIRE (sel->check_node_visibility(3) == data_selection::deleted); + REQUIRE (sel->check_node_visibility(4) == data_selection::exists); + REQUIRE (sel->check_node_visibility(5) == data_selection::non_exist); + + test_formatter f; + sel->write_nodes(f); + + REQUIRE (f.m_nodes.size() == 4); + + REQUIRE( + test_formatter::node_t( + element_info(1, 1, 1, "2013-11-14T02:10:00Z", 1, std::string("user_1"), true), + 0.0, 0.0, + tags_t() + ) == f.m_nodes[0]); + + REQUIRE( + test_formatter::node_t( + element_info(2, 1, 1, "2013-11-14T02:10:01Z", 1, std::string("user_1"), true), + 0.1, 0.1, + tags_t() + ) == f.m_nodes[1]); + + REQUIRE( + test_formatter::node_t( + element_info(3, 2, 2, "2015-03-02T18:27:00Z", 1, std::string("user_1"), false), + 0.0, 0.0, + tags_t() + ) == f.m_nodes[2]); + + REQUIRE( + test_formatter::node_t( + element_info(4, 1, 4, "2015-03-02T19:25:00Z", {}, {}, true), + 0.0, 0.0, + tags_t() + ) == f.m_nodes[3]); + } } -void test_psql_array_to_vector() { - std::string test = "{NULL}"; - std::vector actual_values; - std::vector values = psql_array_to_vector(test); +TEST_CASE_METHOD( DatabaseTestsFixture, "test_dup_nodes", "[nodes][db]" ) { - if (values != actual_values) { - throw std::runtime_error("Psql array parse failed for " + test); - } + auto sel = tdb.get_data_selection(); - test = "{1,2}"; - values = psql_array_to_vector(test); - actual_values.clear(); - actual_values.push_back("1"); - actual_values.push_back("2"); - if (values != actual_values) { - throw std::runtime_error("Psql array parse failed for " + test); - } + SECTION("Initialize test data") { + tdb.run_sql( + "INSERT INTO users (id, email, pass_crypt, creation_time, display_name, data_public) " + "VALUES " + " (1, 'user_1@example.com', '', '2013-11-14T02:10:00Z', 'user_1', true); " - test = "{\"TEST\",TEST123}"; - values = psql_array_to_vector(test); - actual_values.clear(); - actual_values.push_back("TEST"); - actual_values.push_back("TEST123"); - if (values != actual_values) { - throw std::runtime_error("Psql array parse failed for " + test); - } + "INSERT INTO changesets (id, user_id, created_at, closed_at) " + "VALUES " + " (1, 1, '2013-11-14T02:10:00Z', '2013-11-14T03:10:00Z');" - test = R"({"},\"",",{}}\\"})"; - values = psql_array_to_vector(test); - actual_values.clear(); - actual_values.push_back("},\""); - actual_values.push_back(",{}}\\"); - if (values != actual_values) { - throw std::runtime_error("Psql array parse failed for " + test + " " +values[0] + " " +values[1]); + "INSERT INTO current_nodes (id, latitude, longitude, changeset_id, visible, \"timestamp\", tile, version) " + " VALUES " + " (1, 0, 0, 1, true, '2013-11-14T02:10:00Z', 3221225472, 1);" + ); } - // test with semicolon in key - test = R"({use_sidepath,secondary,3,1,yes,50,"Rijksweg Noord",asphalt,left|through;right})"; - values = psql_array_to_vector(test); - actual_values.clear(); - actual_values.push_back("use_sidepath"); - actual_values.push_back("secondary"); - actual_values.push_back("3"); - actual_values.push_back("1"); - actual_values.push_back("yes"); - actual_values.push_back("50"); - actual_values.push_back("Rijksweg Noord"); - actual_values.push_back("asphalt"); - actual_values.push_back("left|through;right"); - if (values != actual_values) { - throw std::runtime_error("Psql array parse failed for " + test + " " +values[0] + " " +values[1]); - } -} + SECTION("Perform duplicate node checks") { -} // anonymous namespace + REQUIRE (sel->check_node_visibility(1) == data_selection::exists); -int main(int, char **) { - try { - test_database tdb; - tdb.setup(); + std::vector ids{1,1,1}; - test_psql_array_to_vector(); + REQUIRE (sel->select_nodes(ids) == 1); + REQUIRE (sel->select_nodes(ids) == 0); // Re-selecting the same node failed - tdb.run(std::function( - &test_single_nodes)); + REQUIRE (sel->check_node_visibility(1) == data_selection::exists); - tdb.run(std::function( - &test_dup_nodes)); + test_formatter f; + sel->write_nodes(f); - } catch (const test_database::setup_error &e) { - std::cout << "Unable to set up test database: " << e.what() << std::endl; - return 77; + REQUIRE (f.m_nodes.size() == 1); - } catch (const std::exception &e) { - std::cout << "Error: " << e.what() << std::endl; - return 1; + REQUIRE( + test_formatter::node_t( + element_info(1, 1, 1, "2013-11-14T02:10:00Z", 1, std::string("user_1"), true), + 0.0, 0.0, + tags_t() + ) == f.m_nodes[0]); + } +} - } catch (...) { - std::cout << "Unknown exception type." << std::endl; - return 99; +TEST_CASE("test_psql_array_to_vector", "[nodb]") { + + std::string test; + std::vector actual_values; + std::vector values; + + SECTION("NULL") { + test = "{NULL}"; + values = psql_array_to_vector(test); + REQUIRE (values == actual_values); + } + + SECTION("Two values") { + test = "{1,2}"; + values = psql_array_to_vector(test); + actual_values = {"1", "2"}; + REQUIRE (values == actual_values); + } + + SECTION("Two strings") { + test = "{\"TEST\",TEST123}"; + values = psql_array_to_vector(test); + actual_values = { "TEST", "TEST123" }; + REQUIRE (values == actual_values); + } + + SECTION("Complex pattern") { + test = R"({"},\"",",{}}\\"})"; + values = psql_array_to_vector(test); + actual_values.push_back("},\""); + actual_values.push_back(",{}}\\"); + REQUIRE (values == actual_values); } - return 0; + SECTION("test with semicolon in key") { + test = R"({use_sidepath,secondary,3,1,yes,50,"Rijksweg Noord",asphalt,left|through;right})"; + values = psql_array_to_vector(test); + actual_values.push_back("use_sidepath"); + actual_values.push_back("secondary"); + actual_values.push_back("3"); + actual_values.push_back("1"); + actual_values.push_back("yes"); + actual_values.push_back("50"); + actual_values.push_back("Rijksweg Noord"); + actual_values.push_back("asphalt"); + actual_values.push_back("left|through;right"); + REQUIRE (values == actual_values); + } }