matrix-coro

A WIP highlevel matrix c++ SDK with a MAS first approach
git clone git://archive.git.mtrnord.blog/MTRNord/matrix-coro.git
Log | Files | Refs | README | LICENSE

test.cpp (14030B)


      1 #include <iostream>
      2 
      3 #include "matrix_coro.hpp"
      4 #include "json.hpp"
      5 #include <catch2/catch_test_macros.hpp>
      6 #include "spdlog/spdlog.h"
      7 
      8 #include "cppcoro/sync_wait.hpp"
      9 
     10 class ClientTest
     11 {
     12 public:
     13     static cppcoro::task<WellKnownResponse> test_fetch_wellknown(Client& client, const std::string& homeserver)
     14     {
     15         return client.fetch_wellknown(homeserver);
     16     }
     17 
     18     static cppcoro::task<AuthIssuerResponse> test_fetch_auth_issuer(Client& client,
     19                                                                     const std::string& cs_endpoint)
     20     {
     21         return client.fetch_auth_issuer(cs_endpoint);
     22     }
     23 
     24     static cppcoro::task<ClientRegistrationResponse> test_register_client(Client& client,
     25                                                                           const std::string& registration_endpoint,
     26                                                                           const ClientRegistrationData&
     27                                                                           registration_data)
     28     {
     29         return client.register_client(registration_endpoint, registration_data);
     30     }
     31 
     32     static std::string test_generate_authorize_url(Client& client,
     33                                                    const std::string& auth_endpoint,
     34                                                    const ClientRegistrationResponse& auth_data,
     35                                                    const std::string& redirect_url,
     36                                                    const std::string& state,
     37                                                    const std::string& code_verifier)
     38     {
     39         return client.generate_authorize_url(auth_endpoint, auth_data, redirect_url, state, code_verifier);
     40     }
     41 
     42     static cppcoro::task<OpenIDConfiguration> fetch_openid_configuration(Client& client,
     43                                                                          const std::string& auth_endpoint)
     44     {
     45         return client.fetch_openid_configuration(auth_endpoint);
     46     }
     47 
     48     static cppcoro::task<TokenResponse> exchange_code_for_token(const Client& client,
     49                                                                 const std::string& token_endpoint,
     50                                                                 const std::string& client_id,
     51                                                                 const std::string& code,
     52                                                                 const std::string& redirect_uri,
     53                                                                 const std::string& code_verifier)
     54     {
     55         return client.exchange_code_for_token(token_endpoint, code, code_verifier, client_id, redirect_uri);
     56     }
     57 };
     58 
     59 void initLogging()
     60 {
     61     spdlog::set_level(spdlog::level::debug);
     62     spdlog::set_pattern("[%H:%M:%S %z] [%^%L%$] [thread %t] %v");
     63 }
     64 
     65 
     66 SCENARIO("fetch_wellknown can find and parse well-known at matrix.org")
     67 {
     68     initLogging();
     69     GIVEN("A Client instance")
     70     {
     71         WHEN("fetch_wellknown is called with matrix.org")
     72         {
     73             Client client;
     74             auto task = ClientTest::test_fetch_wellknown(client, "matrix.org");
     75             auto [homeserver, identity_server, raw] = sync_wait(task);
     76             THEN("A valid WellKnownResponse should be returned")
     77             {
     78                 REQUIRE(homeserver == "https://matrix-client.matrix.org");
     79                 REQUIRE(identity_server == "https://vector.im");
     80             }
     81         }
     82     }
     83 }
     84 
     85 SCENARIO("fetch_wellknown throws runtime_error if curl_easy_perform fails")
     86 {
     87     initLogging();
     88     GIVEN("A Client instance with an invalid URL")
     89     {
     90         WHEN("fetch_wellknown is called with an invalid URL")
     91         {
     92             THEN("A runtime_error should be thrown")
     93             {
     94                 Client client;
     95                 REQUIRE_THROWS_AS(sync_wait(ClientTest::test_fetch_wellknown(client,"invalid_url")),
     96                                   std::runtime_error);
     97             }
     98         }
     99     }
    100 }
    101 
    102 SCENARIO("fetch_wellknown throws runtime_error if JSON parsing fails")
    103 {
    104     initLogging();
    105     GIVEN("A Client instance with a URL returning invalid JSON")
    106     {
    107         WHEN("fetch_wellknown is called with a URL returning invalid JSON")
    108         {
    109             THEN("A runtime_error should be thrown")
    110             {
    111                 Client client;
    112                 REQUIRE_THROWS_AS(
    113                     sync_wait(ClientTest::test_fetch_wellknown(client,"https://example.com/invalid-json")),
    114                     std::runtime_error);
    115             }
    116         }
    117     }
    118 }
    119 
    120 SCENARIO("fetch_auth_issuer can find and parse auth issuer at https://synapse-oidc.element.dev")
    121 {
    122     initLogging();
    123     GIVEN("A Client instance")
    124     {
    125         WHEN("fetch_auth_issuer is called with https://synapse-oidc.element.dev")
    126         {
    127             Client client;
    128             auto task = ClientTest::test_fetch_auth_issuer(client, "https://synapse-oidc.element.dev");
    129             auto [issuer] = sync_wait(task);
    130             THEN("A valid AuthIssuerResponse should be returned")
    131             {
    132                 REQUIRE(issuer == "https://auth-oidc.element.dev/");
    133             }
    134         }
    135     }
    136 }
    137 
    138 SCENARIO("fetch_auth_issuer throws runtime_error if curl_easy_perform fails")
    139 {
    140     initLogging();
    141     GIVEN("A Client instance with an invalid URL")
    142     {
    143         WHEN("fetch_auth_issuer is called with an invalid URL")
    144         {
    145             THEN("A runtime_error should be thrown")
    146             {
    147                 Client client;
    148                 REQUIRE_THROWS_AS(sync_wait(ClientTest::test_fetch_auth_issuer(client,"invalid_url")),
    149                                   std::runtime_error);
    150             }
    151         }
    152     }
    153 }
    154 
    155 SCENARIO("fetch_auth_issuer throws runtime_error if JSON parsing fails")
    156 {
    157     initLogging();
    158     GIVEN("A Client instance with a URL returning invalid JSON")
    159     {
    160         WHEN("fetch_auth_issuer is called with a URL returning invalid JSON")
    161         {
    162             THEN("A runtime_error should be thrown")
    163             {
    164                 Client client;
    165                 REQUIRE_THROWS_AS(
    166                     sync_wait(ClientTest::test_fetch_auth_issuer(client,"https://example.com/invalid-json")),
    167                     std::runtime_error);
    168             }
    169         }
    170     }
    171 }
    172 
    173 SCENARIO("register_client can register a client at https://synapse-oidc.element.dev")
    174 {
    175     initLogging();
    176     GIVEN("A Client instance")
    177     {
    178         WHEN("register_client is called with https://synapse-oidc.element.dev")
    179         {
    180             Client client;
    181             ClientRegistrationData registration_data;
    182             registration_data.application_type = "web";
    183             registration_data.client_name = "Test Client";
    184             registration_data.client_uri = "https://example.com";
    185             registration_data.token_endpoint_auth_method = "none";
    186             registration_data.redirect_uris = {"https://example.com"};
    187             registration_data.response_types = {"code"};
    188             registration_data.grant_types = {"authorization_code", "refresh_token"};
    189             registration_data.contacts = {"mailto:hello@example.com"};
    190             auto task = ClientTest::test_register_client(client, "https://auth-oidc.element.dev/oauth2/registration",
    191                                                          registration_data);
    192             auto [client_id, client_id_issued_at] = sync_wait(task);
    193             THEN("A valid ClientRegistrationResponse should be returned")
    194             {
    195                 REQUIRE(!client_id.empty());
    196                 REQUIRE(client_id_issued_at > 0);
    197             }
    198         }
    199     }
    200 }
    201 
    202 SCENARIO("register_client throws runtime_error if curl_easy_perform fails")
    203 {
    204     initLogging();
    205     GIVEN("A Client instance with an invalid URL")
    206     {
    207         WHEN("register_client is called with an invalid URL")
    208         {
    209             THEN("A runtime_error should be thrown")
    210             {
    211                 Client client;
    212                 ClientRegistrationData registration_data;
    213                 registration_data.application_type = "web";
    214                 registration_data.client_name = "Test Client";
    215                 registration_data.client_uri = "https://example.com";
    216                 registration_data.token_endpoint_auth_method = "none";
    217                 registration_data.redirect_uris = {"https://example.com"};
    218                 registration_data.response_types = {"code"};
    219                 registration_data.grant_types = {"authorization_code", "refresh_token"};
    220                 registration_data.contacts = {"mailto:hello@example.com"};
    221                 REQUIRE_THROWS_AS(sync_wait(ClientTest::test_register_client(client,"invalid_url", registration_data)),
    222                                   std::runtime_error);
    223             }
    224         }
    225     }
    226 }
    227 
    228 SCENARIO("register_client throws runtime_error if JSON parsing fails")
    229 {
    230     initLogging();
    231     GIVEN("A Client instance with a URL returning invalid JSON")
    232     {
    233         WHEN("register_client is called with a URL returning invalid JSON")
    234         {
    235             THEN("A runtime_error should be thrown")
    236             {
    237                 Client client;
    238                 ClientRegistrationData registration_data;
    239                 registration_data.application_type = "web";
    240                 registration_data.client_name = "Test Client";
    241                 registration_data.client_uri = "https://example.com";
    242                 registration_data.token_endpoint_auth_method = "none";
    243                 registration_data.redirect_uris = {"https://example.com"};
    244                 registration_data.response_types = {"code"};
    245                 registration_data.grant_types = {"authorization_code", "refresh_token"};
    246                 registration_data.contacts = {"mailto:hello@example.com"};
    247                 REQUIRE_THROWS_AS(
    248                     sync_wait(ClientTest::test_register_client(client,"https://example.com/invalid-json",
    249                         registration_data)),
    250                     std::runtime_error);
    251             }
    252         }
    253     }
    254 }
    255 
    256 SCENARIO("generate_authorize_url can generate a valid authorize URL")
    257 {
    258     initLogging();
    259     GIVEN("A Client")
    260     {
    261         WHEN("generate_authorize_url is called")
    262         {
    263             Client client;
    264             ClientRegistrationResponse auth_data;
    265             auth_data.client_id = "test_client_id";
    266             auth_data.client_id_issued_at = 1630000000;
    267             const std::string redirect_url = "https://example.com";
    268             const std::string state = "test_state";
    269             const std::string code_verifier = "test_code_verifier";
    270             auto authorize_url = ClientTest::test_generate_authorize_url(
    271                 client, "https://auth-oidc.element.dev/authorize",
    272                 auth_data, redirect_url, state, code_verifier);
    273             THEN("A valid authorize URL should be returned")
    274             {
    275                 REQUIRE(
    276                     authorize_url ==
    277                     "https://auth-oidc.element.dev/authorize?response_type=code&response_mode=fragment&client_id=test_client_id&redirect_uri=https%3A%2F%2Fexample.com&scope=urn%3Amatrix%3Aorg.matrix.msc2967.client%3Aapi%3A*%20urn%3Amatrix%3Aorg.matrix.msc2967.client%3Adevice%3AABCDEFGHIJKL&state=test_state&code_challenge_method=S256&code_challenge="
    278                     + (cthash::base64url_encode(cthash::simple<cthash::sha256>(code_verifier)).to_string()));
    279             }
    280         }
    281     }
    282 }
    283 
    284 // Fetch openid configuration
    285 SCENARIO("fetch_openid_configuration can find and parse openid configuration at https://auth-oidc.element.dev")
    286 {
    287     initLogging();
    288     GIVEN("A Client instance")
    289     {
    290         WHEN("fetch_openid_configuration is called with https://auth-oidc.element.dev")
    291         {
    292             Client client;
    293             auto task = ClientTest::fetch_openid_configuration(client, "https://auth-oidc.element.dev");
    294             auto resp = sync_wait(task);
    295             THEN("A valid OpenIDConfiguration should be returned")
    296             {
    297                 REQUIRE(resp.issuer == "https://auth-oidc.element.dev/");
    298                 REQUIRE(resp.authorization_endpoint == "https://auth-oidc.element.dev/authorize");
    299                 REQUIRE(resp.token_endpoint == "https://auth-oidc.element.dev/oauth2/token");
    300                 REQUIRE(resp.jwks_uri == "https://auth-oidc.element.dev/oauth2/keys.json");
    301                 REQUIRE(resp.registration_endpoint == "https://auth-oidc.element.dev/oauth2/registration");
    302                 REQUIRE(resp.revocation_endpoint == "https://auth-oidc.element.dev/oauth2/revoke");
    303                 REQUIRE(resp.introspection_endpoint == "https://auth-oidc.element.dev/oauth2/introspect");
    304                 REQUIRE(resp.userinfo_endpoint == "https://auth-oidc.element.dev/oauth2/userinfo");
    305                 REQUIRE(resp.device_authorization_endpoint == "https://auth-oidc.element.dev/oauth2/device");
    306                 REQUIRE(resp.account_management_uri == "https://auth-oidc.element.dev/account/");
    307             }
    308         }
    309     }
    310 }
    311 
    312 SCENARIO("fetch_openid_configuration throws runtime_error if curl_easy_perform fails")
    313 {
    314     initLogging();
    315     GIVEN("A Client instance with an invalid URL")
    316     {
    317         WHEN("fetch_openid_configuration is called with an invalid URL")
    318         {
    319             THEN("A runtime_error should be thrown")
    320             {
    321                 Client client;
    322                 REQUIRE_THROWS_AS(sync_wait(ClientTest::fetch_openid_configuration(client,"invalid_url")),
    323                                   std::runtime_error);
    324             }
    325         }
    326     }
    327 }
    328 
    329 SCENARIO("fetch_openid_configuration throws runtime_error if JSON parsing fails")
    330 {
    331     initLogging();
    332     GIVEN("A Client instance with a URL returning invalid JSON")
    333     {
    334         WHEN("fetch_openid_configuration is called with a URL returning invalid JSON")
    335         {
    336             THEN("A runtime_error should be thrown")
    337             {
    338                 Client client;
    339                 REQUIRE_THROWS_AS(
    340                     sync_wait(ClientTest::fetch_openid_configuration(client,"https://example.com/invalid-json")),
    341                     std::runtime_error);
    342             }
    343         }
    344     }
    345 }