/* This file is part of TALER Copyright (C) 2014-2018 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. TALER is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with TALER; see the file COPYING. If not, see */ /** * @file lib/testing_api_cmd_history.c * @brief command to test the /history API. * @author Marcello Stanisci */ #include "platform.h" #include #include #include "taler_merchant_service.h" #include "taler_merchant_testing_lib.h" /** * State for a "history" CMD. */ struct HistoryState { /** * Expected status code. */ unsigned int http_status; /** * The merchant instance executing this CMD. */ const char *instance; /** * URL of the merchant backend serving the /history request. */ const char *merchant_url; /** * The interpreter state. */ struct TALER_TESTING_Interpreter *is; /** * Handle to the /history operation. */ struct TALER_MERCHANT_HistoryOperation *ho; /** * Only history entries younger than this * value will be returned. */ struct GNUNET_TIME_Absolute time; /** * First row index we want in the results. */ unsigned long long start; /** * When this flag is GNUNET_YES, then the interpreter * will request /history *omitting* the 'start' URL argument. */ int use_default_start; /** * How many rows we want the response to contain, at most. */ long long nrows; /** * Expected number of history entries returned by the * backend. */ unsigned int nresult; }; /** * Parse given JSON object to absolute time. * * @param root the json object representing data * @param[out] ret where to write the data * @return #GNUNET_OK upon successful parsing; * #GNUNET_SYSERR upon error */ static int parse_abs_time (json_t *root, struct GNUNET_TIME_Absolute *ret) { const char *val; unsigned long long int tval; val = json_string_value (root); if (NULL == val) { GNUNET_break_op (0); return GNUNET_SYSERR; } if ( (0 == strcasecmp (val, "/forever/")) || (0 == strcasecmp (val, "/end of time/")) || (0 == strcasecmp (val, "/never/")) ) { *ret = GNUNET_TIME_UNIT_FOREVER_ABS; return GNUNET_OK; } if (1 != sscanf (val, "/Date(%llu)/", &tval)) { GNUNET_break_op (0); return GNUNET_SYSERR; } /* Time is in seconds in JSON, but in microseconds in * GNUNET_TIME_Absolute */ ret->abs_value_us = tval * 1000LL * 1000LL; if ( (ret->abs_value_us) / 1000LL / 1000LL != tval) { /* Integer overflow */ GNUNET_break_op (0); return GNUNET_SYSERR; } return GNUNET_OK; } /** * Callback for a /history request; checks that (1) HTTP status * is expected, the number of rows returned is expected, and that * the rows are sorted from the youngest to the oldest record. * * @param cls closure * @param http_status HTTP status returned by the merchant * backend * @param ec taler-specific error code * @param json actual body containing the history */ static void history_cb (void *cls, unsigned int http_status, enum TALER_ErrorCode ec, const json_t *json) { struct HistoryState *hs = cls; unsigned int nresult; struct GNUNET_TIME_Absolute last_timestamp; struct GNUNET_TIME_Absolute entry_timestamp; hs->ho = NULL; if (hs->http_status != http_status) TALER_TESTING_FAIL (hs->is); if (0 == hs->http_status) { /* 0 was caused intentionally by the tests, * move on without further checking. */ TALER_TESTING_interpreter_next (hs->is); return; } nresult = json_array_size (json); if (hs->nresult != nresult) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected number of history entries." " Got %d, expected %d\n", nresult, hs->nresult); TALER_TESTING_FAIL (hs->is); } last_timestamp = GNUNET_TIME_absolute_get (); last_timestamp = GNUNET_TIME_absolute_add (last_timestamp, GNUNET_TIME_UNIT_DAYS); json_t *entry; json_t *timestamp; size_t index; json_array_foreach (json, index, entry) { timestamp = json_object_get (entry, "timestamp"); if (GNUNET_OK != parse_abs_time (timestamp, &entry_timestamp)) TALER_TESTING_FAIL (hs->is); if (last_timestamp.abs_value_us < entry_timestamp.abs_value_us) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "History entries are NOT" " sorted from younger to older\n"); TALER_TESTING_interpreter_fail (hs->is); return; } last_timestamp = entry_timestamp; } TALER_TESTING_interpreter_next (hs->is); } /** * Free the state for a "history" CMD, and possibly cancel * any pending operation thereof. * * @param cls closure * @param cmd command being freed now. */ static void history_cleanup (void *cls, const struct TALER_TESTING_Command *cmd) { struct HistoryState *hs = cls; if (NULL != hs->ho) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "/history operation did not complete\n"); TALER_MERCHANT_history_cancel (hs->ho); } GNUNET_free (hs); } /** * Run a "history" CMD. * * @param cls closure. * @param cmd current command. * @param is interpreter state. */ static void history_run (void *cls, const struct TALER_TESTING_Command *cmd, struct TALER_TESTING_Interpreter *is) { struct HistoryState *hs = cls; hs->is = is; if (0 == hs->time.abs_value_us) { hs->time = GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get (), GNUNET_TIME_UNIT_HOURS); GNUNET_TIME_round_abs (&hs->time); } switch (hs->use_default_start) { case GNUNET_YES: hs->ho = TALER_MERCHANT_history_default_start (is->ctx, hs->merchant_url, "default", hs->nrows, hs->time, &history_cb, hs); break; case GNUNET_NO: hs->ho = TALER_MERCHANT_history (is->ctx, hs->merchant_url, "default", hs->start, hs->nrows, hs->time, &history_cb, hs); break; default: TALER_LOG_ERROR ("Bad value for 'use_default_start'\n"); TALER_TESTING_FAIL (is); } if (NULL == hs->ho) TALER_TESTING_FAIL (is); } /** * Make a "history" command. * * @param label command label. * @param merchant_url base URL of the merchant serving the * request. * @param ctx CURL context. * @param http_status expected HTTP response code * @param time limit towards the past for the history * records we want returned. * @param nresult how many results are expected * @param start first row id we want in the result. * @param use_default_start if GNUNET_YES, then it will * use the API call that requests /history omitting * the 'start' argument. This makes easier to test * the server default behaviour. * @param nrows how many row we want to receive, at most. */ static struct TALER_TESTING_Command cmd_history2 (const char *label, const char *merchant_url, unsigned int http_status, struct GNUNET_TIME_Absolute time, unsigned int nresult, unsigned long long start, int use_default_start, long long nrows) { struct HistoryState *hs; hs = GNUNET_new (struct HistoryState); hs->http_status = http_status; hs->time = time; hs->nresult = nresult; hs->start = start; hs->nrows = nrows; hs->merchant_url = merchant_url; hs->use_default_start = use_default_start; struct TALER_TESTING_Command cmd = { .cls = hs, .label = label, .run = &history_run, .cleanup = &history_cleanup }; return cmd; } /** * Make a "history" command. * * @param label command label. * @param merchant_url base URL of the merchant serving the * request. * @param ctx CURL context. * @param http_status expected HTTP response code * @param time limit towards the past for the history * records we want returned. * @param nresult how many results are expected * @param nrows how many row we want to receive, at most. */ struct TALER_TESTING_Command TALER_TESTING_cmd_history_default_start (const char *label, const char *merchant_url, unsigned int http_status, struct GNUNET_TIME_Absolute time, unsigned int nresult, long long nrows) { return cmd_history2 (label, merchant_url, http_status, time, nresult, -1, /* ignored */ GNUNET_YES, nrows); } /** * Make a "history" command. * * @param label command label. * @param merchant_url base URL of the merchant serving the * request. * @param ctx CURL context. * @param http_status expected HTTP response code * @param time limit towards the past for the history * records we want returned. * @param nresult how many results are expected * @param start first row id we want in the result. * @param nrows how many row we want to receive, at most. */ struct TALER_TESTING_Command TALER_TESTING_cmd_history (const char *label, const char *merchant_url, unsigned int http_status, struct GNUNET_TIME_Absolute time, unsigned int nresult, unsigned long long start, long long nrows) { return cmd_history2 (label, merchant_url, http_status, time, nresult, start, GNUNET_NO, nrows); } /* end of testing_api_cmd_history.c */