challenger

OAuth 2.0-based authentication service that validates user can receive messages at a certain address
Log | Files | Refs | Submodules | README | LICENSE

test_challenger_db.c (23769B)


      1 /*
      2   This file is part of Challenger
      3   (C) 2023 Taler Systems SA
      4 
      5   Challenger is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   Challenger is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12 
     13   You should have received a copy of the GNU General Public License along with
     14   Challenger; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file challengerdb/test_challenger_db.c
     18  * @brief testcase for challenger postgres db plugin
     19  * @author Christian Grothoff
     20  */
     21 #include "platform.h"
     22 #include <gnunet/gnunet_util_lib.h>
     23 #include <gnunet/gnunet_pq_lib.h>
     24 #include <taler/taler_util.h>
     25 #include "challenger_database_lib.h"
     26 #include "challenger-database/drop_tables.h"
     27 #include "challenger-database/create_tables.h"
     28 #include "challenger-database/preflight.h"
     29 #include "challenger-database/gc.h"
     30 #include "challenger-database/insert_client.h"
     31 #include "challenger-database/do_insert_validation.h"
     32 #include "challenger-database/do_challenge_address.h"
     33 #include "challenger-database/do_solve_challenge.h"
     34 #include "challenger-database/do_insert_token.h"
     35 #include "challenger-database/get_token.h"
     36 #include "challenger-database/get_validation_pkce.h"
     37 #include "challenger_util.h"
     38 #include "pg_helper.h"
     39 #include "challenger-database/delete_client.h"
     40 #include "challenger-database/do_insert_validation.h"
     41 
     42 
     43 #define FAILIF(cond)                            \
     44         do {                                          \
     45           if (! (cond)) { break;}                       \
     46           GNUNET_break (0);                           \
     47           goto drop;                                     \
     48         } while (0)
     49 
     50 /**
     51  * Secret of the client the tests register and act as.
     52  */
     53 #define CLIENT_SECRET "secret-token:test-secret"
     54 
     55 /**
     56  * Redirect URI of the client the tests register.
     57  */
     58 #define CLIENT_URI "http://client.example.com/"
     59 
     60 /**
     61  * Global return value for the test.  Initially -1, set to 0 upon
     62  * completion.   Other values indicate some kind of error.
     63  */
     64 static int result;
     65 
     66 /**
     67  * Handle to the database we are testing.
     68  */
     69 static struct CHALLENGERDB_PostgresContext *pg;
     70 
     71 /**
     72  * Serial ID of the client registered by #setup_client().
     73  */
     74 static uint64_t client_id;
     75 
     76 
     77 /**
     78  * Register the OAuth client all tests act as.
     79  *
     80  * @return #GNUNET_OK on success
     81  */
     82 static enum GNUNET_GenericReturnValue
     83 setup_client (void)
     84 {
     85   bool uri_taken = false;
     86 
     87   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     88       CHALLENGERDB_insert_client (pg,
     89                                   CLIENT_URI,
     90                                   CLIENT_SECRET,
     91                                   &client_id,
     92                                   &uri_taken))
     93   {
     94     GNUNET_break (0);
     95     return GNUNET_SYSERR;
     96   }
     97   return GNUNET_OK;
     98 }
     99 
    100 
    101 /**
    102  * Create a fresh validation for #client_id and have a TAN transmitted
    103  * for it, leaving the validation in the state a user reaches by posting
    104  * an address to ``/challenge`` and then walking away.
    105  *
    106  * @param[out] nonce set to the nonce identifying the new validation
    107  * @param[out] pin set to the TAN that was "transmitted"
    108  * @return #GNUNET_OK on success
    109  */
    110 static enum GNUNET_GenericReturnValue
    111 challenge_validation (struct CHALLENGER_ValidationNonceP *nonce,
    112                       uint32_t *pin)
    113 {
    114   struct GNUNET_TIME_Absolute expiration
    115     = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS);
    116   json_t *address;
    117   char *state = NULL;
    118   char *redirect_uri = NULL;
    119   struct GNUNET_TIME_Absolute last_tx_time;
    120   uint32_t auth_attempts_left;
    121   uint32_t pin_transmissions_left;
    122   bool pin_transmit;
    123   bool address_refused;
    124   bool solved;
    125   enum GNUNET_DB_QueryStatus qs;
    126 
    127   GNUNET_CRYPTO_random_block (nonce,
    128                               sizeof (*nonce));
    129   qs = CHALLENGERDB_do_insert_validation (pg,
    130                                           client_id,
    131                                           CLIENT_SECRET,
    132                                           nonce,
    133                                           expiration,
    134                                           NULL);
    135   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
    136   {
    137     GNUNET_break (0);
    138     return GNUNET_SYSERR;
    139   }
    140   address = json_pack ("{s:s}",
    141                        "filename",
    142                        "test-challenger-db.txt");
    143   GNUNET_assert (NULL != address);
    144   *pin = 424242;
    145   qs = CHALLENGERDB_do_challenge_address (pg,
    146                                           nonce,
    147                                           address,
    148                                           GNUNET_TIME_UNIT_ZERO,
    149                                           pin,
    150                                           &state,
    151                                           &last_tx_time,
    152                                           &auth_attempts_left,
    153                                           &pin_transmissions_left,
    154                                           &pin_transmit,
    155                                           &redirect_uri,
    156                                           &address_refused,
    157                                           &solved);
    158   json_decref (address);
    159   /* The TAN is only pending until its transmission is confirmed; this
    160      stands in for a successful AUTH_COMMAND run. */
    161   qs = CHALLENGERDB_do_challenge_address_confirm_pin (pg,
    162                                                       nonce,
    163                                                       &auth_attempts_left);
    164   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
    165   {
    166     GNUNET_break (0);
    167     return GNUNET_SYSERR;
    168   }
    169   GNUNET_free (state);
    170   GNUNET_free (redirect_uri);
    171   if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) ||
    172        (! pin_transmit) ||
    173        address_refused ||
    174        solved)
    175   {
    176     GNUNET_break (0);
    177     return GNUNET_SYSERR;
    178   }
    179   return GNUNET_OK;
    180 }
    181 
    182 
    183 /**
    184  * Enter @a pin for the validation under @a nonce and check it was accepted.
    185  *
    186  * @param nonce validation to solve
    187  * @param pin TAN to enter
    188  * @return #GNUNET_OK on success
    189  */
    190 static enum GNUNET_GenericReturnValue
    191 solve_validation (const struct CHALLENGER_ValidationNonceP *nonce,
    192                   uint32_t pin)
    193 {
    194   char *state = NULL;
    195   char *redirect_uri = NULL;
    196   uint32_t addr_left;
    197   uint32_t auth_attempts_left;
    198   uint32_t pin_transmissions_left;
    199   bool solved;
    200   bool exhausted;
    201   bool no_challenge;
    202   enum GNUNET_DB_QueryStatus qs;
    203 
    204   qs = CHALLENGERDB_do_solve_challenge (pg,
    205                                         nonce,
    206                                         pin,
    207                                         &solved,
    208                                         &exhausted,
    209                                         &no_challenge,
    210                                         &state,
    211                                         &addr_left,
    212                                         &auth_attempts_left,
    213                                         &pin_transmissions_left,
    214                                         &redirect_uri);
    215   GNUNET_free (state);
    216   GNUNET_free (redirect_uri);
    217   if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) ||
    218        (! solved) ||
    219        exhausted ||
    220        no_challenge)
    221   {
    222     GNUNET_break (0);
    223     return GNUNET_SYSERR;
    224   }
    225   return GNUNET_OK;
    226 }
    227 
    228 
    229 /**
    230  * Mint an access token for the validation under @a nonce.
    231  *
    232  * @param client client redeeming the authorization code
    233  * @param nonce validation to redeem
    234  * @param[out] token set to the (random) access token we tried to store
    235  * @return transaction status of CHALLENGERDB_do_insert_token()
    236  */
    237 static enum GNUNET_DB_QueryStatus
    238 insert_token (uint64_t client,
    239               const struct CHALLENGER_ValidationNonceP *nonce,
    240               struct CHALLENGER_AccessTokenP *token)
    241 {
    242   GNUNET_CRYPTO_random_block (token,
    243                               sizeof (*token));
    244   return CHALLENGERDB_do_insert_token (pg,
    245                                        nonce,
    246                                        client,
    247                                        token,
    248                                        GNUNET_TIME_UNIT_HOURS,
    249                                        GNUNET_TIME_UNIT_DAYS);
    250 }
    251 
    252 
    253 /**
    254  * Test that a validation for which the user never entered the correct
    255  * TAN cannot be redeemed for an access token, and is not even visible to
    256  * the ``/token`` lookup.  Without the ``auth_attempts_left < 0`` guard
    257  * anyone able to compute the authorization code could mint an
    258  * attestation for an address nobody ever proved control over.
    259  *
    260  * @return #GNUNET_OK on success
    261  */
    262 static enum GNUNET_GenericReturnValue
    263 test_unsolved_not_redeemable (void)
    264 {
    265   struct CHALLENGER_ValidationNonceP nonce;
    266   struct CHALLENGER_AccessTokenP token;
    267   uint32_t pin;
    268 
    269   if (GNUNET_OK !=
    270       challenge_validation (&nonce,
    271                             &pin))
    272     return GNUNET_SYSERR;
    273   {
    274     char *client_secret;
    275     json_t *address;
    276     char *client_scope;
    277     char *client_state;
    278     char *client_redirect_uri;
    279     char *code_challenge;
    280     uint32_t code_challenge_method;
    281 
    282     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    283         CHALLENGERDB_get_validation_pkce (pg,
    284                                           &nonce,
    285                                           client_id,
    286                                           &client_secret,
    287                                           &address,
    288                                           &client_scope,
    289                                           &client_state,
    290                                           &client_redirect_uri,
    291                                           &code_challenge,
    292                                           &code_challenge_method))
    293     {
    294       GNUNET_break (0);
    295       return GNUNET_SYSERR;
    296     }
    297   }
    298   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    299       insert_token (client_id,
    300                     &nonce,
    301                     &token))
    302   {
    303     GNUNET_break (0);
    304     return GNUNET_SYSERR;
    305   }
    306   return GNUNET_OK;
    307 }
    308 
    309 
    310 /**
    311  * Test that a solved validation is redeemable, and redeemable exactly
    312  * once (the authorization code must not be replayable).
    313  *
    314  * @return #GNUNET_OK on success
    315  */
    316 static enum GNUNET_GenericReturnValue
    317 test_solved_redeemable (void)
    318 {
    319   struct CHALLENGER_ValidationNonceP nonce;
    320   struct CHALLENGER_AccessTokenP token;
    321   uint32_t pin;
    322 
    323   if ( (GNUNET_OK !=
    324         challenge_validation (&nonce,
    325                               &pin)) ||
    326        (GNUNET_OK !=
    327         solve_validation (&nonce,
    328                           pin)) )
    329     return GNUNET_SYSERR;
    330   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    331       insert_token (client_id,
    332                     &nonce,
    333                     &token))
    334   {
    335     GNUNET_break (0);
    336     return GNUNET_SYSERR;
    337   }
    338   /* replay: the validation was consumed, so this must not mint a token */
    339   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    340       insert_token (client_id,
    341                     &nonce,
    342                     &token))
    343   {
    344     GNUNET_break (0);
    345     return GNUNET_SYSERR;
    346   }
    347   return GNUNET_OK;
    348 }
    349 
    350 
    351 /**
    352  * Test that the address expiration stored with a freshly minted token is
    353  * in the future, and in particular that it does *not* depend on the
    354  * validation's ``last_tx_time``.  The regression this guards against
    355  * computed the expiration as ``address_expiration + last_tx_time``, which
    356  * lands in 1970 whenever that column was never written.  We force the
    357  * column back to 0 to pin the property down without relying on any
    358  * particular code path leaving it there.
    359  *
    360  * @return #GNUNET_OK on success
    361  */
    362 static enum GNUNET_GenericReturnValue
    363 test_address_expiry_is_in_the_future (void)
    364 {
    365   struct GNUNET_PQ_ExecuteStatement es[] = {
    366     GNUNET_PQ_make_execute ("UPDATE validations SET last_tx_time=0;"),
    367     GNUNET_PQ_EXECUTE_STATEMENT_END
    368   };
    369   struct CHALLENGER_ValidationNonceP nonce;
    370   struct CHALLENGER_AccessTokenP token;
    371   struct GNUNET_TIME_Timestamp address_expiration;
    372   struct GNUNET_TIME_Absolute now;
    373   json_t *address = NULL;
    374   uint64_t rowid;
    375   uint32_t pin;
    376   enum GNUNET_GenericReturnValue ret;
    377 
    378   if ( (GNUNET_OK !=
    379         challenge_validation (&nonce,
    380                               &pin)) ||
    381        (GNUNET_OK !=
    382         solve_validation (&nonce,
    383                           pin)) )
    384     return GNUNET_SYSERR;
    385   if (GNUNET_OK !=
    386       GNUNET_PQ_exec_statements (pg->conn,
    387                                  es))
    388   {
    389     GNUNET_break (0);
    390     return GNUNET_SYSERR;
    391   }
    392   now = GNUNET_TIME_absolute_get ();
    393   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    394       insert_token (client_id,
    395                     &nonce,
    396                     &token))
    397   {
    398     GNUNET_break (0);
    399     return GNUNET_SYSERR;
    400   }
    401   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    402       CHALLENGERDB_get_token (pg,
    403                               &token,
    404                               &rowid,
    405                               &address,
    406                               &address_expiration))
    407   {
    408     GNUNET_break (0);
    409     return GNUNET_SYSERR;
    410   }
    411   ret = GNUNET_OK;
    412   if (address_expiration.abs_time.abs_value_us <= now.abs_value_us)
    413   {
    414     GNUNET_break (0);
    415     ret = GNUNET_SYSERR;
    416   }
    417   json_decref (address);
    418   return ret;
    419 }
    420 
    421 
    422 /**
    423  * Test that an expired validation is not consumable, even though the
    424  * caller (``/token``) checked the expiration in an earlier, separate
    425  * transaction.  Everything get_validation_pkce() filters on has to be
    426  * repeated by do_insert_token(), or the gap between the two statements is
    427  * an unguarded TOCTOU window.
    428  *
    429  * @return #GNUNET_OK on success
    430  */
    431 static enum GNUNET_GenericReturnValue
    432 test_expired_not_redeemable (void)
    433 {
    434   struct GNUNET_PQ_ExecuteStatement es[] = {
    435     GNUNET_PQ_make_execute ("UPDATE validations SET expiration_time=1;"),
    436     GNUNET_PQ_EXECUTE_STATEMENT_END
    437   };
    438   struct CHALLENGER_ValidationNonceP nonce;
    439   struct CHALLENGER_AccessTokenP token;
    440   uint32_t pin;
    441 
    442   if ( (GNUNET_OK !=
    443         challenge_validation (&nonce,
    444                               &pin)) ||
    445        (GNUNET_OK !=
    446         solve_validation (&nonce,
    447                           pin)) )
    448     return GNUNET_SYSERR;
    449   if (GNUNET_OK !=
    450       GNUNET_PQ_exec_statements (pg->conn,
    451                                  es))
    452   {
    453     GNUNET_break (0);
    454     return GNUNET_SYSERR;
    455   }
    456   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    457       insert_token (client_id,
    458                     &nonce,
    459                     &token))
    460   {
    461     GNUNET_break (0);
    462     return GNUNET_SYSERR;
    463   }
    464   return GNUNET_OK;
    465 }
    466 
    467 
    468 /**
    469  * Test that a validation cannot be consumed by a client it does not
    470  * belong to.
    471  *
    472  * @return #GNUNET_OK on success
    473  */
    474 static enum GNUNET_GenericReturnValue
    475 test_foreign_client_not_redeemable (void)
    476 {
    477   struct CHALLENGER_ValidationNonceP nonce;
    478   struct CHALLENGER_AccessTokenP token;
    479   uint32_t pin;
    480 
    481   if ( (GNUNET_OK !=
    482         challenge_validation (&nonce,
    483                               &pin)) ||
    484        (GNUNET_OK !=
    485         solve_validation (&nonce,
    486                           pin)) )
    487     return GNUNET_SYSERR;
    488   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    489       insert_token (client_id + 1,
    490                     &nonce,
    491                     &token))
    492   {
    493     GNUNET_break (0);
    494     return GNUNET_SYSERR;
    495   }
    496   /* ... and the rightful client still can */
    497   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    498       insert_token (client_id,
    499                     &nonce,
    500                     &token))
    501   {
    502     GNUNET_break (0);
    503     return GNUNET_SYSERR;
    504   }
    505   return GNUNET_OK;
    506 }
    507 
    508 
    509 /**
    510  * Run @a sql directly on the database, bypassing the CHALLENGERDB API.
    511  * Used to create states (and to make assertions about the schema) that
    512  * the API deliberately cannot produce.
    513  *
    514  * @param sql SQL statement to execute
    515  * @return #GNUNET_OK on success
    516  */
    517 static enum GNUNET_GenericReturnValue
    518 exec_sql (const char *sql)
    519 {
    520   struct GNUNET_PQ_ExecuteStatement es[] = {
    521     GNUNET_PQ_make_execute (sql),
    522     GNUNET_PQ_EXECUTE_STATEMENT_END
    523   };
    524 
    525   return GNUNET_PQ_exec_statements (pg->conn,
    526                                     es);
    527 }
    528 
    529 
    530 /**
    531  * Test that a client cannot be inserted silently: a URI that is already
    532  * registered must be reported as such, and any *other* unique violation
    533  * (notably one on the primary key, which happens once the identity sequence
    534  * of 'clients' lags behind max(client_serial_id)) must not be misreported as
    535  * a duplicate URI.
    536  *
    537  * @return #GNUNET_OK on success
    538  */
    539 static enum GNUNET_GenericReturnValue
    540 test_insert_client (void)
    541 {
    542   uint64_t nclient_id;
    543   bool uri_taken;
    544 
    545   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    546       CHALLENGERDB_insert_client (pg,
    547                                   "https://example.com/a",
    548                                   "secret-token:a",
    549                                   &nclient_id,
    550                                   &uri_taken))
    551   {
    552     GNUNET_break (0);
    553     return GNUNET_SYSERR;
    554   }
    555   if (uri_taken)
    556   {
    557     GNUNET_break (0);
    558     return GNUNET_SYSERR;
    559   }
    560   /* Same URI again: this one really is a duplicate. */
    561   if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    562         CHALLENGERDB_insert_client (pg,
    563                                     "https://example.com/a",
    564                                     "secret-token:b",
    565                                     &nclient_id,
    566                                     &uri_taken)) ||
    567        (! uri_taken) )
    568   {
    569     GNUNET_break (0);
    570     return GNUNET_SYSERR;
    571   }
    572   /* Rewind the identity sequence, as a restore that did not restore the
    573      sequences leaves it: the next INSERT then collides on the primary key,
    574      not on the URI. */
    575   if (GNUNET_OK !=
    576       exec_sql ("DO $$ BEGIN"
    577                 " PERFORM setval("
    578                 "   pg_get_serial_sequence('challenger.clients',"
    579                 "                          'client_serial_id'),"
    580                 "   (SELECT MIN(client_serial_id) FROM challenger.clients),"
    581                 "   false);"
    582                 "END $$;"))
    583   {
    584     GNUNET_break (0);
    585     return GNUNET_SYSERR;
    586   }
    587   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
    588       CHALLENGERDB_insert_client (pg,
    589                                   "https://example.com/b",
    590                                   "secret-token:b",
    591                                   &nclient_id,
    592                                   &uri_taken))
    593   {
    594     /* The PK collision must not go unnoticed. */
    595     GNUNET_break (0);
    596     return GNUNET_SYSERR;
    597   }
    598   if (uri_taken)
    599   {
    600     /* This is the regression: the failure has nothing to do with the URI,
    601        and the operator must not be told that the URI already exists. */
    602     GNUNET_break (0);
    603     return GNUNET_SYSERR;
    604   }
    605   /* Repair the sequence, then the very same insert must work. */
    606   if (GNUNET_OK !=
    607       exec_sql ("DO $$ BEGIN"
    608                 " PERFORM setval("
    609                 "   pg_get_serial_sequence('challenger.clients',"
    610                 "                          'client_serial_id'),"
    611                 "   (SELECT MAX(client_serial_id) FROM challenger.clients),"
    612                 "   true);"
    613                 "END $$;"))
    614   {
    615     GNUNET_break (0);
    616     return GNUNET_SYSERR;
    617   }
    618   if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    619         CHALLENGERDB_insert_client (pg,
    620                                     "https://example.com/b",
    621                                     "secret-token:b",
    622                                     &nclient_id,
    623                                     &uri_taken)) ||
    624        (uri_taken) )
    625   {
    626     GNUNET_break (0);
    627     return GNUNET_SYSERR;
    628   }
    629   return GNUNET_OK;
    630 }
    631 
    632 
    633 /**
    634  * Test that deleting a client reports how many validations it took with it:
    635  * validations_client_serial_id_fkey cascades, so an operator would otherwise
    636  * abort in-flight KYC processes without being told.
    637  *
    638  * @return #GNUNET_OK on success
    639  */
    640 static enum GNUNET_GenericReturnValue
    641 test_delete_client (void)
    642 {
    643   const char *uri = "https://example.com/del";
    644   const char *secret = "secret-token:del";
    645   struct GNUNET_TIME_Absolute expiration
    646     = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS);
    647   uint64_t nclient_id;
    648   uint64_t validations_deleted;
    649   bool uri_taken;
    650 
    651   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    652       CHALLENGERDB_insert_client (pg,
    653                                   uri,
    654                                   secret,
    655                                   &nclient_id,
    656                                   &uri_taken))
    657   {
    658     GNUNET_break (0);
    659     return GNUNET_SYSERR;
    660   }
    661   for (unsigned int i = 0; i < 2; i++)
    662   {
    663     struct CHALLENGER_ValidationNonceP nonce;
    664 
    665     GNUNET_CRYPTO_random_block (&nonce,
    666                                 sizeof (nonce));
    667     if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    668         CHALLENGERDB_do_insert_validation (pg,
    669                                            nclient_id,
    670                                            secret,
    671                                            &nonce,
    672                                            expiration,
    673                                            NULL))
    674     {
    675       GNUNET_break (0);
    676       return GNUNET_SYSERR;
    677     }
    678   }
    679   if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    680         CHALLENGERDB_delete_client (pg,
    681                                     uri,
    682                                     &validations_deleted)) ||
    683        (2 != validations_deleted) )
    684   {
    685     GNUNET_break (0);
    686     return GNUNET_SYSERR;
    687   }
    688   /* Deleting it again finds nothing and discards nothing. */
    689   if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    690         CHALLENGERDB_delete_client (pg,
    691                                     uri,
    692                                     &validations_deleted)) ||
    693        (0 != validations_deleted) )
    694   {
    695     GNUNET_break (0);
    696     return GNUNET_SYSERR;
    697   }
    698   return GNUNET_OK;
    699 }
    700 
    701 
    702 /**
    703  * Main function that will be run by the scheduler.
    704  *
    705  * @param cls closure with config
    706  */
    707 static void
    708 run (void *cls)
    709 {
    710   struct GNUNET_CONFIGURATION_Handle *cfg = cls;
    711 
    712   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    713               "Connecting\n");
    714   if (NULL == (pg = CHALLENGERDB_connect_admin (cfg)))
    715   {
    716     result = 77;
    717     return;
    718   }
    719   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    720               "Connected\n");
    721   if (GNUNET_OK !=
    722       CHALLENGERDB_drop_tables (pg))
    723   {
    724     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    725                 "Dropping tables failed\n");
    726   }
    727   if (GNUNET_OK !=
    728       CHALLENGERDB_create_tables (pg))
    729   {
    730     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    731                 "Creating tables failed\n");
    732     goto drop;
    733   }
    734   GNUNET_assert (GNUNET_OK ==
    735                  CHALLENGERDB_preflight (pg));
    736   {
    737     struct GNUNET_TIME_Absolute ts = GNUNET_TIME_absolute_get ();
    738 
    739     FAILIF (0 >
    740             CHALLENGERDB_gc (pg,
    741                              ts));
    742   }
    743   FAILIF (GNUNET_OK !=
    744           setup_client ());
    745   FAILIF (GNUNET_OK !=
    746           test_unsolved_not_redeemable ());
    747   FAILIF (GNUNET_OK !=
    748           test_solved_redeemable ());
    749   FAILIF (GNUNET_OK !=
    750           test_address_expiry_is_in_the_future ());
    751   FAILIF (GNUNET_OK !=
    752           test_expired_not_redeemable ());
    753   FAILIF (GNUNET_OK !=
    754           test_foreign_client_not_redeemable ());
    755   FAILIF (GNUNET_OK !=
    756           test_insert_client ());
    757   FAILIF (GNUNET_OK !=
    758           test_delete_client ());
    759   result = 0;
    760 drop:
    761   GNUNET_break (GNUNET_OK ==
    762                 CHALLENGERDB_drop_tables (pg));
    763   CHALLENGERDB_disconnect (pg);
    764   pg = NULL;
    765 }
    766 
    767 
    768 int
    769 main (int argc,
    770       char *const argv[])
    771 {
    772   struct GNUNET_CONFIGURATION_Handle *cfg;
    773 
    774   (void) argc;
    775   result = EXIT_FAILURE;
    776   GNUNET_log_setup (argv[0],
    777                     "DEBUG",
    778                     NULL);
    779   cfg = GNUNET_CONFIGURATION_create (CHALLENGER_project_data ());
    780   if (GNUNET_OK !=
    781       GNUNET_CONFIGURATION_parse (cfg,
    782                                   "test_challenger_db_postgres.conf"))
    783   {
    784     GNUNET_break (0);
    785     return EXIT_NOTCONFIGURED;
    786   }
    787   GNUNET_SCHEDULER_run (&run, cfg);
    788   GNUNET_CONFIGURATION_destroy (cfg);
    789   return result;
    790 }
    791 
    792 
    793 /* end of test_challenger_db.c */