From 6db31ff96d48972d399851d01105bc4e851c6cde Mon Sep 17 00:00:00 2001 From: Sam Garrett Date: Wed, 31 Dec 2014 15:19:35 -0500 Subject: [PATCH] maint - adding tests that mock curl calls, separating integration tests, updated README --- .gitignore | 1 + .travis.yml | 4 +- Makefile.am | 6 ++ README.md | 25 +++-- configure.ac | 1 + src/Makefile.am | 36 ++++--- src/test/minunit.h | 55 ++++++++--- src/test/test.h | 66 +++++++++++++ src/test/test_reply.c | 83 ++++++++++------ src/test/test_twitc.c | 159 ++++++++++++++++-------------- src/test/test_twitc_integration.c | 111 +++++++++++++++++++++ src/twitc.c | 82 +++++++++------ src/twitc.h | 8 +- src/twitc_reply.c | 74 ++++++++++---- src/twitc_reply.h | 5 +- 15 files changed, 524 insertions(+), 192 deletions(-) create mode 100644 src/test/test.h create mode 100644 src/test/test_twitc_integration.c diff --git a/.gitignore b/.gitignore index 2fd90fe..3c9273a 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,4 @@ stamp-h1 test-drive src/test/test_reply src/test/test_twitc +src/test/integration diff --git a/.travis.yml b/.travis.yml index 8c081f8..21d0547 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,12 +5,14 @@ before_install: install: libtoolize && ./autogen.sh && ./configure compiler: - gcc -script: make check +script: make check && make check-valgrind env: global: - TWITC_KEY=A TWITC_SECRET=B TWITC_ACCESS_TOKEN=C TWITC_ACCESS_SECRET=D - CFLAGS="-I/home/travis/include -L/home/travis/lib" after_failure: + - ls /home/travis/build/sinemetu1/twitc/src + - ls /home/travis/build/sinemetu1/twitc/src/test - cat /home/travis/build/sinemetu1/twitc/src/test-suite.log - cat /home/travis/build/sinemetu1/twitc/src/test/test_reply.log - cat /home/travis/build/sinemetu1/twitc/src/test/test_twitc.log diff --git a/Makefile.am b/Makefile.am index a4a3a43..08f3d88 100644 --- a/Makefile.am +++ b/Makefile.am @@ -5,3 +5,9 @@ dist_doc_DATA = README.md ACLOCAL_AMFLAGS = -I m4 README: README.md + +integration: + cd src; make integration; + +check-valgrind: + cd src; make check-valgrind; diff --git a/README.md b/README.md index ca5a84b..8c5aa4d 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,6 @@ A mini C library for interacting with the Twitter OAuth api. ### Installation: - ./autogen.sh ./configure make @@ -15,7 +14,8 @@ A mini C library for interacting with the Twitter OAuth api. NOTE: There are environment variables that can be set for consumer token, consumer secret, app token, and app secret. See -[twitc.h](https://github.com/sinemetu1/twitc/blob/master/src/twitc.h). +[twitc.h](https://github.com/sinemetu1/twitc/blob/master/src/twitc.h) +and the [usage section](https://github.com/sinemetu1/twitc/tree/test-separation#usage) below. #### Dependencies: @@ -29,21 +29,26 @@ See [main.c](https://github.com/sinemetu1/twitc/blob/master/src/main.c) for an e Environment variables include: - TWITC_KEY - TWITC_SECRET - TWITC_ACCESS_TOKEN - TWITC_ACCESS_SECRET + export TWITC_KEY="Consumer Key (API Key)" + export TWITC_SECRET="Consumer Secret (API Secret)" + export TWITC_ACCESS_TOKEN="Access Token" + export TWITC_ACCESS_SECRET="Access Token Secret" To generate these values go to [https://apps.twitter.com/](https://apps.twitter.com/). ### Tests: -Tests can be run with `make check` if you have the environment variables above -setup correctly and uncommenting test calls in `twitc_all_tests` in -[src/test/test_twitc.c](https://github.com/sinemetu1/twitc/blob/master/src/test/test_twitc.c) -will help you debug any issues. +Local tests can be run with `make check`. + +If you have the environment variables above setup correctly then +you can run `make integration` to test actual integration with +Twitter's API. ### Bugs: Please file bugs in Github issues. + +### License: + +[MIT](https://github.com/sinemetu1/twitc/blob/test-separation/LICENSE) diff --git a/configure.ac b/configure.ac index 632a073..fe7215f 100644 --- a/configure.ac +++ b/configure.ac @@ -24,6 +24,7 @@ AC_SUBST([CFLAGS]) AC_PROG_CC AM_PROG_CC_C_O +AC_PROG_CC_C99 AM_PROG_AR AC_LANG([C]) LT_INIT diff --git a/src/Makefile.am b/src/Makefile.am index a30c625..c073c0d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -3,27 +3,39 @@ AUTOMAKE_OPTIONS = subdir-objects TWITC_H = \ twitc_reply.h \ twitc.h + TWITC_C = \ twitc_reply.c \ twitc.c -bin_PROGRAMS = twitc -include_HEADERS = $(TWITC_H) -twitc_SOURCES = main.c $(TWITC_H) $(TWITC_C) +TEST_TWITC_H = \ + test/test.h -lib_LIBRARIES = libtwitc.a +include_HEADERS = $(TWITC_H) +lib_LIBRARIES = libtwitc.a libtwitc_a_SOURCES = $(TWITC_H) $(TWITC_C) SUBDIRS = test + check_PROGRAMS = \ - test/test_reply \ - test/test_twitc + test/test_twitc \ + test/test_reply + TESTS = $(check_PROGRAMS) -test_test_reply_CFLAGS = $(AM_CFLAGS) -test_test_reply_SOURCES = test/test_reply.c -test_test_reply_LDADD = $(lib_LIBRARIES) +test_test_reply_CFLAGS = -DTEST_TWITC $(CFLAGS) $(AM_CFLAGS) +test_test_reply_SOURCES = $(TEST_TWITC_H) $(TWITC_H) $(TWITC_C) test/test_reply.c + +test_test_twitc_CFLAGS = -DTEST_TWITC $(CFLAGS) $(AM_CFLAGS) +test_test_twitc_SOURCES = $(TEST_TWITC_H) $(TWITC_H) $(TWITC_C) test/test_twitc.c + +EXTRA_PROGRAMS = test/integration +test_integration_CFLAGS = $(CFLAGS) $(AM_CFLAGS) +test_integration_SOURCES = $(TWITC_H) $(TWITC_C) test/test_twitc_integration.c + +check-valgrind: + valgrind --leak-check=full --track-origins=yes test/test_twitc + valgrind --leak-check=full --track-origins=yes test/test_reply -test_test_twitc_CFLAGS = $(AM_CFLAGS) -test_test_twitc_SOURCES = test/test_twitc.c -test_test_twitc_LDADD = $(lib_LIBRARIES) +integration: test/integration + test/integration diff --git a/src/test/minunit.h b/src/test/minunit.h index 0bf0534..f62558f 100644 --- a/src/test/minunit.h +++ b/src/test/minunit.h @@ -1,16 +1,49 @@ -/* file: minunit.h */ -/* taken from http://www.jera.com/techinfo/jtns/jtn002.html#Source_Code */ +/* file: minunit.h + * taken and modified from http://www.jera.com/techinfo/jtns/jtn002.html#Source_Code + */ + #include +#include + #define mu_assert(message, test) do { if (!(test)) return message; } while (0) -#define mu_run_test(file, line, func, test) do { \ - char *message = test(); \ - int ret_length = 1024; \ - char *toRet = malloc(ret_length); \ - memset(toRet, 0, ret_length); \ - tests_run++; \ - if (message) { \ + +#define mu_run_test(file, line, func, test) do { \ + char *message = test(); \ + tests_run++; \ + if (message) { \ + int ret_length = 1024; \ + char *toRet = malloc(ret_length); \ + memset(toRet, 0, ret_length); \ sprintf(toRet, "Failed %s at line %d: %s", __FILE__, __LINE__, message); \ - return toRet; \ - } \ + return toRet; \ + } \ } while (0) + +/** + * Tests actual for equality with processed string. + */ +#define mu_assert_str(message, actual, format, ...) do { \ + char local[BUFSIZ] = ""; \ + snprintf(local, sizeof(local), format, __VA_ARGS__);\ + int cmp = strcmp(local, actual); \ + if (cmp != 0) { \ + printf("%s != %s\n", local, actual); \ + return message; \ + } \ + } while (0) + +/** + * Tests actual for containment of processed string. + */ +#define mu_assert_strcspn(message, actual, format, ...) do {\ + char local[BUFSIZ] = ""; \ + snprintf(local, BUFSIZ, format, __VA_ARGS__); \ + char *cmp = strstr(actual, local); \ + if (cmp == NULL) { \ + printf("didn't find %s in actual\n", local); \ + printf("actual: %s\n", actual); \ + return message; \ + } \ + } while (0) + extern int tests_run; diff --git a/src/test/test.h b/src/test/test.h new file mode 100644 index 0000000..05b7c4b --- /dev/null +++ b/src/test/test.h @@ -0,0 +1,66 @@ +/** + * @file test.h + * @author Sam Garrett + */ +#ifndef TEST_TWITC_H +#define TEST_TWITC_H + +#include + +void print_tracked_params(void); +void free_tracked_params(void); + +/** + * NOTE: will not work if using in multi-threaded tests + * because of global vars below. + */ +#define test_curl_easy_setopt(handle, option, paremeter, track) do { \ + curl_easy_setopt(handle, option, paremeter); \ + int length = strlen(paremeter) + 1; \ + if (track == NULL) { \ + track = malloc(length); \ + if (track == NULL) { \ + fprintf(stderr, "not enough memory (malloc returned NULL)\n"); \ + exit(1); \ + } \ + } else { \ + track = realloc(track, length); \ + if (track == NULL) { \ + fprintf(stderr, "not enough memory (realloc returned NULL)\n");\ + exit(1); \ + } \ + } \ + snprintf(track, length, "%s", paremeter); \ + } while (0) + +/** + * Can't really have a drop-in replacement for curl_easy_perform + * since it's return value is used, thus this one with an extra param. + */ +#define test_curl_easy_perform(handle, curl_res) do { \ + test_curl_perform_called++; \ + curl_res = CURLE_OK; \ + } while (0) + +int test_curl_perform_called = 0; +char *test_curl_url; +char *test_curl_post_data; +char *test_curl_header; + +void +print_tracked_params(void) +{ + printf("perform_called: %d\ncalled_url: %s\nposted_data: %s\ntest_curl_header: %s\n", + test_curl_perform_called, test_curl_url, test_curl_post_data, + test_curl_header); +} + +void +free_tracked_params(void) +{ + if (test_curl_url) { free(test_curl_url); } + if (test_curl_post_data) { free(test_curl_post_data); } + if (test_curl_header) { free(test_curl_header); } +} + +#endif diff --git a/src/test/test_reply.c b/src/test/test_reply.c index 2f532b3..0b1f7b4 100644 --- a/src/test/test_reply.c +++ b/src/test/test_reply.c @@ -1,4 +1,4 @@ -/* basic tests for reply */ +/* basic tests for twitc_reply */ #include #include @@ -8,9 +8,10 @@ int tests_run = 0; -char *test_twitc_parse_json(void); -char *test_twitc_reply_has_error(void); -char *reply_all_tests(void); +char * test_twitc_parse_json(void); +char * test_twitc_reply_has_error(void); +char * test_twitc_reply_has_error_true(void); +char * reply_all_tests(void); int main(void) @@ -26,53 +27,73 @@ main(void) return result != 0; } -char -*test_twitc_parse_json(void) +char * +reply_all_tests(void) +{ + mu_run_test(__FILE__, __LINE__, __func__, test_twitc_parse_json); + mu_run_test(__FILE__, __LINE__, __func__, test_twitc_reply_has_error); + mu_run_test(__FILE__, __LINE__, __func__, test_twitc_reply_has_error_true); + return 0; +} + +char * +test_twitc_parse_json(void) { printf("\nChecking test_twitc_parse_json...\n\n"); - const char* author = "John Cage"; - char contents[BUFSIZ]; + const char *author = "John Cage"; + char *reply_format = "{ \"%s\": { \"%s\": \"%s\" } }"; + char contents[BUFSIZ] = ""; char *json; + int doesnt_have_error = 1; - sprintf(contents, "{ \"%s\": { \"%s\": \"%s\" } }", + snprintf(contents, sizeof(contents), reply_format, TWITC_REPLY_USER_KEY, TWITC_REPLY_SN_KEY, author); - json = twitc_parse_json_author((void *)contents); - mu_assert("should parse json", json); + json = twitc_parse_json_author(contents); + mu_assert_str("should parse json", json, "%s", author); + + doesnt_have_error = twitc_reply_has_error(contents); + mu_assert("should NOT indicate error for json", doesnt_have_error == 0); + + if (json) { free(json); } return 0; } -char -*test_twitc_reply_has_error(void) +char * +test_twitc_reply_has_error(void) { printf("\nChecking twitc_reply_has_error...\n\n"); - char contents[BUFSIZ]; - int has_error; - int doesnt_have_error; + const char *author = "John Cage"; + char *reply_format = "{ \"%s\": { \"%s\": \"%s\" } }"; + char contents[BUFSIZ] = ""; + int doesnt_have_error = 1; - sprintf(contents, "{ \"%s\": { \"%s\": \"an error occurred\" } }", - TWITC_REPLY_ERRORS, TWITC_REPLY_ERRORS_MESSAGE); - - has_error = twitc_reply_has_error((void *)contents); - mu_assert("should indicate error for error json", has_error == 1); - - // negative check, no error - sprintf(contents, "{ \"%s\": { \"%s\": \"John Cage\" } }", - TWITC_REPLY_USER_KEY, TWITC_REPLY_SN_KEY); + snprintf(contents, sizeof(contents), reply_format, + TWITC_REPLY_USER_KEY, TWITC_REPLY_SN_KEY, author); - doesnt_have_error = twitc_reply_has_error((void *)contents); - mu_assert("should indicate error for error json", doesnt_have_error == 0); + doesnt_have_error = twitc_reply_has_error(contents); + mu_assert("should NOT indicate error for json", doesnt_have_error == 0); return 0; } -char -*reply_all_tests(void) +char * +test_twitc_reply_has_error_true(void) { - mu_run_test(__FILE__, __LINE__, __func__, test_twitc_parse_json); - mu_run_test(__FILE__, __LINE__, __func__, test_twitc_reply_has_error); + printf("\nChecking test_twitc_reply_has_error_true...\n\n"); + + char *reply_format = "{ \"%s\": \"\" }"; + char contents[BUFSIZ] = ""; + int has_error = 0; + + snprintf(contents, sizeof(contents), reply_format, + TWITC_REPLY_ERRORS); + + has_error = twitc_reply_has_error(contents); + mu_assert("should indicate error for error json", has_error == 1); + return 0; } diff --git a/src/test/test_twitc.c b/src/test/test_twitc.c index fd84a38..779f969 100644 --- a/src/test/test_twitc.c +++ b/src/test/test_twitc.c @@ -4,17 +4,23 @@ #include #include #include +#include #include "minunit.h" #include "twitc.h" #include "twitc_reply.h" +extern int test_curl_perform_called; +extern char *test_curl_url; +extern char *test_curl_post_data; +extern char *test_curl_header; + int tests_run = 0; -char *test_twitc_oauth_request_token(void); -char *test_twitc_oauth_authorize_token(void); -char *test_twitc_oauth_access_token(void); -char *test_twitc_oauth_twitter(void); -char *twitc_all_tests(void); +void free_tracked_params(void); +char * test_twitc_oauth_request_token(void); +char * test_twitc_oauth_access_token(void); +char * test_twitc_oauth_twitter(void); +char * twitc_all_tests(void); int main(void) @@ -28,91 +34,91 @@ main(void) } printf("Tests run: %d\n", tests_run); + free_tracked_params(); + return result != 0; } -char -*test_twitc_oauth_request_token(void) +char * +twitc_all_tests(void) { - printf("\nChecking test_twitc_oauth_request_token...\n\n"); - - char *token = NULL; - char *secret = NULL; - char *c_key = getenv(CONSUMER_KEY); - char *c_secret = getenv(CONSUMER_SECRET); - - twitc_oauth_request_token(c_key, c_secret, &token, &secret); - - mu_assert("should be able to get a token", token != NULL); - mu_assert("token length should be 32", strlen(token) == 32); - mu_assert("should be able to get a secret", secret != NULL); - mu_assert("secret length should be 32", strlen(secret) == 32); - + mu_run_test(__FILE__, __LINE__, __func__, test_twitc_oauth_request_token); + mu_run_test(__FILE__, __LINE__, __func__, test_twitc_oauth_access_token); + mu_run_test(__FILE__, __LINE__, __func__, test_twitc_oauth_twitter); return 0; } -char -*test_twitc_oauth_authorize_token(void) +char * +test_twitc_oauth_request_token(void) { - printf("\nChecking test_twitc_oauth_authorize_token...\n\n"); + printf("\nChecking test_twitc_oauth_request_token...\n\n"); + int cache_count = test_curl_perform_called; char *token = NULL; char *secret = NULL; - char *c_key = getenv(CONSUMER_KEY); - char *c_secret = getenv(CONSUMER_SECRET); - char *pin = malloc(BUFSIZ); - if (pin == NULL) { - fprintf(stderr, "malloc failed"); - exit(1); - } + char *c_key = "C"; + char *c_secret = "D"; twitc_oauth_request_token(c_key, c_secret, &token, &secret); - twitc_oauth_authorize_token(token, &pin); - mu_assert("should be able to authorize a token", strlen(pin) > 0); + mu_assert("verify called", (cache_count+1) == test_curl_perform_called); + mu_assert_str("verify called url", test_curl_url, "%s?", REQUEST_TOKEN_URL); + mu_assert_strcspn("verify oauth_consumer_key header", + test_curl_header, "%s=\"%s\"", "oauth_consumer_key", "C"); + mu_assert_str("verify no post_data", test_curl_post_data, "%s", "(null)"); - free(pin); + if (token) { free(token); } + if (secret) { free(secret); } return 0; } -char -*test_twitc_oauth_access_token(void) +char * +test_twitc_oauth_access_token(void) { printf("\nChecking test_twitc_oauth_access_token...\n\n"); + int cache_count = test_curl_perform_called; - char *access_token = NULL; - char *access_secret = NULL; - char pin[BUFSIZ]; - char *token = getenv(TWITC_ACCESS_TOKEN); - char *secret = getenv(TWITC_ACCESS_SECRET); - char *c_key = getenv(CONSUMER_KEY); - char *c_secret = getenv(CONSUMER_SECRET); + char *access_token = "access_token"; + char *access_secret = "access_secret"; + char *pin = "XYZ"; + char *token = "A"; + char *secret = "B"; + char *c_key = "C"; + char *c_secret = "D"; twitc_oauth_access_token(c_key, c_secret, token, secret, pin, &access_token, &access_secret); - mu_assert("should be able to do get an access token", access_token != NULL); - mu_assert("access_token length should be 32", strlen(access_token) == 32); - mu_assert("should be able to do get an access secret", access_secret != NULL); - mu_assert("access_secret length should be 32", strlen(access_secret) == 32); + mu_assert("verify called", (cache_count+1) == test_curl_perform_called); + mu_assert_str("verify called url", test_curl_url, "%s?", ACCESS_TOKEN_URL); + mu_assert_strcspn("verify oauth_consumer_key header", + test_curl_header, "%s=\"%s\"", "oauth_consumer_key", "C"); + mu_assert_strcspn("verify oauth_token header", + test_curl_header, "%s=\"%s\"", TWITC_OAUTH_TOKEN, token); + mu_assert_str("verify no post_data", test_curl_post_data, "%s", "(null)"); return 0; } -char -*test_twitc_oauth_twitter(void) +char * +test_twitc_oauth_twitter(void) { printf("\nChecking test_twitc_oauth_twitter...\n\n"); + int cache_count = test_curl_perform_called; + CURL *curl = curl_easy_init(); // used for checking escaping in post_data struct MemoryStruct *reply; int has_errors; - char tweet_url[BUFSIZ]; - char *token = getenv(TWITC_ACCESS_TOKEN); - char *secret = getenv(TWITC_ACCESS_SECRET); - char *c_key = getenv(CONSUMER_KEY); - char *c_secret = getenv(CONSUMER_SECRET); + char tweet_url[BUFSIZ] = ""; + char *token = "A"; + char *secret = "B"; + char *c_key = "C"; + char *c_secret = "D"; int randNumber = rand(); + char *extra_params = "testing=extra_params"; + char *post_string = "hello d'oh!"; + char *escaped_post_string; reply = twitc_oauth_twitter(TEST_TIMELINE_URL, c_key, c_secret, token, secret, TWITC_OAUTH_GET, @@ -120,32 +126,41 @@ char has_errors = twitc_check_memory_for_errors(reply); mu_assert("should be able to do twitter oauth", has_errors == 0); + mu_assert("verify called", (cache_count+1) == test_curl_perform_called); + mu_assert_str("verify called url", test_curl_url, "%s?", TEST_TIMELINE_URL); + mu_assert_strcspn("verify oauth_consumer_key header", + test_curl_header, "%s=\"%s\"", "oauth_consumer_key", "C"); + mu_assert_strcspn("verify oauth_token header", + test_curl_header, "%s=\"%s\"", TWITC_OAUTH_TOKEN, token); + mu_assert_str("verify no post_data", test_curl_post_data, "%s", "(null)"); - if (reply->memory) { - free(reply->memory); - } + if (reply->memory) { free(reply->memory); } + if (reply) { free(reply); } // try with POST to timeline - sprintf(tweet_url, "%s?testing=extra_params&%s=%s%d", USER_STATUS_URL, TWITC_REPLY_STATUS, - "hello d'oh!", randNumber); + snprintf(tweet_url, sizeof(tweet_url), + "%s?%s&%s=%s%d", USER_STATUS_URL, extra_params, TWITC_REPLY_STATUS, + post_string, randNumber); reply = twitc_oauth_twitter(tweet_url, c_key, c_secret, token, secret, TWITC_OAUTH_POST, &twitc_get_memory_callback); has_errors = twitc_check_memory_for_errors(reply); mu_assert("should be able to do POST with twitter oauth", has_errors == 0); + mu_assert("verify called", (cache_count+2) == test_curl_perform_called); + mu_assert_str("verify called url", test_curl_url, "%s", USER_STATUS_URL); + mu_assert_strcspn("verify oauth_consumer_key header", + test_curl_header, "%s=\"%s\"", "oauth_consumer_key", "C"); + mu_assert_strcspn("verify oauth_token header", + test_curl_header, "%s=\"%s\"", TWITC_OAUTH_TOKEN, token); + escaped_post_string = curl_easy_escape(curl, post_string, strlen(post_string)); + mu_assert_str("verify post_data", test_curl_post_data, "%s=%s%d&%s", + TWITC_REPLY_STATUS, escaped_post_string, + randNumber, extra_params); + + if (escaped_post_string) { free(escaped_post_string); } + if (reply->memory) { free(reply->memory); } + if (reply) { free(reply); } + curl_easy_cleanup(curl); - if (reply->memory) { - free(reply->memory); - } - free(reply); - - return 0; -} - -char -*twitc_all_tests(void) -{ - /*mu_run_test(__FILE__, __LINE__, __func__, test_twitc_oauth_request_token);*/ - /*mu_run_test(__FILE__, __LINE__, __func__, test_twitc_oauth_twitter);*/ return 0; } diff --git a/src/test/test_twitc_integration.c b/src/test/test_twitc_integration.c new file mode 100644 index 0000000..d250596 --- /dev/null +++ b/src/test/test_twitc_integration.c @@ -0,0 +1,111 @@ +/* integration tests for twitc */ + +#include +#include +#include +#include +#include "minunit.h" +#include "twitc.h" +#include "twitc_reply.h" + +extern int test_curl_perform_called; +extern char* test_curl_url; +extern char* test_curl_post_data; +extern char* test_curl_header; + +int tests_run = 0; + +char * test_twitc_oauth_twitter_integration(void); +char * test_twitc_oauth_authorize_token_integration(void); +char * twitc_all_tests(void); + +int +main(void) +{ + srand(time(NULL)); + char *result = twitc_all_tests(); + if (result != 0) { + printf("%s\n", result); + } else { + printf("ALL TESTS PASSED :)\n"); + } + printf("Tests run: %d\n", tests_run); + + return result != 0; +} + +char * +twitc_all_tests(void) +{ + mu_run_test(__FILE__, __LINE__, __func__, test_twitc_oauth_authorize_token_integration); + mu_run_test(__FILE__, __LINE__, __func__, test_twitc_oauth_twitter_integration); + return 0; +} + +char * +test_twitc_oauth_twitter_integration(void) +{ + printf("\nChecking test_twitc_oauth_twitter...\n\n"); + + struct MemoryStruct *reply; + int has_errors; + char tweet_url[BUFSIZ] = ""; + char *token = getenv(TWITC_ACCESS_TOKEN); + char *secret = getenv(TWITC_ACCESS_SECRET); + char *c_key = getenv(CONSUMER_KEY); + char *c_secret = getenv(CONSUMER_SECRET); + int randNumber = rand(); + + reply = twitc_oauth_twitter(TEST_TIMELINE_URL, + c_key, c_secret, token, secret, TWITC_OAUTH_GET, + &twitc_get_memory_callback); + + has_errors = twitc_check_memory_for_errors(reply); + mu_assert("should be able to do twitter oauth", has_errors == 0); + + if (reply->memory) { + free(reply->memory); + } + + // try with POST to timeline + snprintf(tweet_url, sizeof(tweet_url), + "%s?testing=extra_params&%s=%s%d", USER_STATUS_URL, TWITC_REPLY_STATUS, + "hello d'oh!", randNumber); + reply = twitc_oauth_twitter(tweet_url, + c_key, c_secret, token, secret, TWITC_OAUTH_POST, + &twitc_get_memory_callback); + has_errors = twitc_check_memory_for_errors(reply); + mu_assert("should be able to do POST with twitter oauth", has_errors == 0); + + if (reply->memory) { + free(reply->memory); + } + free(reply); + + return 0; +} + +char * +test_twitc_oauth_authorize_token_integration(void) +{ + printf("\nChecking test_twitc_oauth_authorize_token...\n\n"); + + char *token = NULL; + char *secret = NULL; + char *c_key = getenv(CONSUMER_KEY); + char *c_secret = getenv(CONSUMER_SECRET); + char *pin = malloc(1); + if (pin == NULL) { + fprintf(stderr, "malloc failed"); + exit(1); + } + + twitc_oauth_request_token(c_key, c_secret, &token, &secret); + + twitc_oauth_authorize_token(token, &pin); + mu_assert("should be able to authorize a token", strlen(pin) > 0); + + free(pin); + + return 0; +} diff --git a/src/twitc.c b/src/twitc.c index 13dec57..1ecc274 100644 --- a/src/twitc.c +++ b/src/twitc.c @@ -3,9 +3,13 @@ #include #include -#include #include #include +#include + +#if defined(TEST_TWITC) +#include "test/test.h" +#endif size_t stream_callback(void *contents, size_t size, size_t nmemb, void *userp) @@ -54,15 +58,28 @@ twitc_do_curl(char *http_hdr, char *req_url, slist = curl_slist_append(slist, http_hdr); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist); curl_easy_setopt(curl, CURLOPT_URL, req_url); + #if defined(TEST_TWITC) + test_curl_easy_setopt(curl, CURLOPT_HTTPHEADER, http_hdr, test_curl_header); + test_curl_easy_setopt(curl, CURLOPT_URL, req_url, test_curl_url); + #endif if (post_data != NULL) { curl_easy_setopt(curl, CURLOPT_POST, 1); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data); + #if defined(TEST_TWITC) + test_curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data, test_curl_post_data); + #endif // Debug: /*fprintf(stderr, "Curl is:\n");*/ /*fprintf(stderr, "curl --request 'POST' '%s' --data '%s' --header '%s' --verbose\n",*/ /*req_url, post_data, http_hdr);*/ } + #if defined(TEST_TWITC) + else { + test_curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "(null)", test_curl_post_data); + } + #endif + // Debug: /*fprintf(stderr, "Curl is:\n");*/ /*fprintf(stderr, "curl --get '%s' --header '%s' --verbose\n", req_url, http_hdr);*/ @@ -73,21 +90,28 @@ twitc_do_curl(char *http_hdr, char *req_url, curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)chunk); } - /*curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);*/ // DO the request - curl_res = curl_easy_perform(curl); + #if !defined(TEST_TWITC) + curl_res = curl_easy_perform(curl); + #else + test_curl_easy_perform(curl, curl_res); + if (chunk != NULL && callback != NULL ) { + char *test_contents = "test contents"; + callback(test_contents, strlen(test_contents), 1, chunk); + } + #endif + + /* we're done with libcurl, so clean it up */ + if (slist) { curl_slist_free_all(slist); } curl_easy_cleanup(curl); + if (curl_res != CURLE_OK) { fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(curl_res)); return -1; } - /* we're done with libcurl, so clean it up */ - if (slist) { curl_slist_free_all(slist); } - curl_global_cleanup(); - return 0; } @@ -132,7 +156,8 @@ twitc_oauth_request(const char *url, } auth_params = oauth_serialize_url_sep(argc, 1, argv, ", ", 6); - sprintf(auth_header, "Authorization: OAuth %s", auth_params); + snprintf(auth_header, sizeof(auth_header), + "Authorization: OAuth %s", auth_params); if (isPost == TWITC_OAUTH_POST) { non_auth_params = oauth_serialize_url_sep(argc, 1, argv, "&", 1 ); @@ -155,6 +180,7 @@ twitc_oauth_request_token(const char *req_c_key, const char *req_c_secret, char **token, char **secret) { int toRet = 0; + char url_and_token[BUFSIZ] = ""; struct MemoryStruct reply; reply.memory = malloc(1); reply.size = 0; @@ -162,8 +188,10 @@ twitc_oauth_request_token(const char *req_c_key, const char *req_c_secret, fprintf(stderr, "not enough memory (malloc returned NULL)\n"); exit(1); } - - twitc_oauth_request(REQUEST_TOKEN_URL, req_c_key, + + snprintf(url_and_token, sizeof(url_and_token), + "%s?oauth_callback=oob", REQUEST_TOKEN_URL); + twitc_oauth_request(url_and_token, req_c_key, req_c_secret, NULL, NULL, &reply, TWITC_OAUTH_GET, &twitc_get_memory_callback); @@ -184,29 +212,24 @@ twitc_oauth_request_token(const char *req_c_key, const char *req_c_secret, int twitc_oauth_authorize_token(char *token, char **pin) { - char *url_and_token = malloc(BUFSIZ); - char *my_pin = malloc(BUFSIZ); - if (url_and_token == NULL || my_pin == NULL) { - fprintf(stderr, "not enough memory (malloc returned NULL)\n"); - exit(1); - } + char url_and_token[BUFSIZ] = ""; + char my_pin[BUFSIZ] = ""; - sprintf(url_and_token, "%s?%s=%s", AUTHORIZE_URL, TWITC_OAUTH_TOKEN, token); + snprintf(url_and_token, sizeof(url_and_token), + "%s?%s=%s", AUTHORIZE_URL, TWITC_OAUTH_TOKEN, token); printf("Go to this url in a browser to authorize this app:\n%s\n", url_and_token); printf("Now input the pin that you have obtained:\n"); scanf("%s", my_pin); - int size = strlen(my_pin); - pin = realloc(pin, (size + 1) * sizeof(char)); - memcpy(pin, &my_pin, size + 1); + int size = strlen(my_pin) + 1; + *pin = realloc(*pin, size); + memcpy(*pin, &my_pin, size); printf("You entered %s.\n", *pin); - free(my_pin); - free(url_and_token); - return 1; + return 0; } int @@ -215,7 +238,7 @@ twitc_oauth_access_token(const char *req_c_key, const char *req_c_secret, char **access_token, char **access_secret) { int toRet = 0; - char url_and_token[BUFSIZ]; + char url_and_token[BUFSIZ] = ""; struct MemoryStruct reply; reply.memory = malloc(1); reply.size = 0; @@ -225,8 +248,7 @@ twitc_oauth_access_token(const char *req_c_key, const char *req_c_secret, exit(1); } - sprintf(url_and_token, "%s?%s=%s&%s=%s", ACCESS_TOKEN_URL, - TWITC_OAUTH_TOKEN, token, + snprintf(url_and_token, sizeof(url_and_token), "%s?%s=%s", ACCESS_TOKEN_URL, TWITC_OAUTH_VERIFIER, pin); twitc_oauth_request(url_and_token, req_c_key, req_c_secret, token, secret, &reply, TWITC_OAUTH_GET, @@ -257,14 +279,14 @@ twitc_check_memory_for_errors(struct MemoryStruct* mem_struct) return 0; } -struct MemoryStruct* +struct MemoryStruct * twitc_oauth_twitter(const char *a_url, const char *req_c_key, const char *req_c_secret, char *user_token, char *user_secret, int isPost, size_t (*callback)(void*, size_t, size_t, void*)) { - char url_and_token[BUFSIZ]; - struct MemoryStruct* reply = malloc(sizeof(*reply)); + char url_and_token[BUFSIZ] = ""; + struct MemoryStruct *reply = malloc(sizeof(*reply)); if (reply == NULL) { fprintf(stderr, "not enough memory (malloc returned NULL)\n"); exit(1); @@ -276,7 +298,7 @@ twitc_oauth_twitter(const char *a_url, const char *req_c_key, } reply->size = 0; - sprintf(url_and_token, "%s", a_url); + snprintf(url_and_token, sizeof(url_and_token), "%s", a_url); twitc_oauth_request(url_and_token, req_c_key, req_c_secret, user_token, user_secret, reply, isPost, diff --git a/src/twitc.h b/src/twitc.h index 4dabaef..414669c 100644 --- a/src/twitc.h +++ b/src/twitc.h @@ -9,10 +9,10 @@ #include // twitc version -#define LIBTWITC_VERSION "0.0.1" +#define LIBTWITC_VERSION "0.0.2" #define LIBTWITC_VERSION_MAJOR 0 #define LIBTWITC_VERSION_MINOR 0 -#define LIBTWITC_VERSION_MICRO 1 +#define LIBTWITC_VERSION_MICRO 2 #define TWITC_OAUTH_TOKEN "oauth_token" #define TWITC_OAUTH_VERIFIER "oauth_verifier" @@ -23,7 +23,7 @@ #define USER_STREAM_URL "https://userstream.twitter.com/1.1/user.json" #define USER_STATUS_URL "https://api.twitter.com/1.1/statuses/update.json" #define TEST_TIMELINE_URL "https://api.twitter.com/1.1/statuses/user_timeline.json" -#define REQUEST_TOKEN_URL "https://api.twitter.com/oauth/request_token?oauth_callback=oob" +#define REQUEST_TOKEN_URL "https://api.twitter.com/oauth/request_token" #define AUTHORIZE_URL "https://api.twitter.com/oauth/authorize" #define ACCESS_TOKEN_URL "https://api.twitter.com/oauth/access_token" @@ -142,7 +142,7 @@ int twitc_oauth_access_token(const char *req_c_key, const char *req_c_secret, * @return a pointer to a MemoryStruct containing the reply from twitter. Caller * should free. */ -struct MemoryStruct* +struct MemoryStruct * twitc_oauth_twitter(const char *a_url, const char *req_c_key, const char *req_c_secret, char *user_token, char *user_secret, int isPost, diff --git a/src/twitc_reply.c b/src/twitc_reply.c index cbee5af..138255a 100644 --- a/src/twitc_reply.c +++ b/src/twitc_reply.c @@ -5,40 +5,71 @@ #include #include -char* -twitc_parse_json_author(void *contents) +static json_object * +parse_wrapper(char *contents) +{ + json_tokener *tok = json_tokener_new(); + json_object *jobj = NULL; + int stringlen = 0; + enum json_tokener_error jerr; + + do { + stringlen = strlen(contents); + jobj = json_tokener_parse_ex(tok, contents, stringlen); + } while ((jerr = json_tokener_get_error(tok)) == json_tokener_continue); + + if (jerr != json_tokener_success) + { + fprintf(stderr, + "Error: %s, couldn't parse: %s\n", + json_tokener_error_desc(jerr), contents); + } + if (tok) { json_tokener_free(tok); } + return jobj; +} + +char * +twitc_parse_json_author(char *contents) { json_object *tweet_obj; json_object *user_json; json_object *author_json; + char *temp; + int length = 0; + char *author = NULL; - tweet_obj = json_tokener_parse((char *) contents); - user_json = json_object_object_get(tweet_obj, TWITC_REPLY_USER_KEY); - - author_json = json_object_object_get(user_json, TWITC_REPLY_SN_KEY); - const char* author = json_object_get_string(author_json); + tweet_obj = parse_wrapper(contents); + if (json_object_object_get_ex(tweet_obj, TWITC_REPLY_USER_KEY, &user_json)) { + if (json_object_object_get_ex(user_json, TWITC_REPLY_SN_KEY, &author_json)) { + temp = (char *) json_object_get_string(author_json); + length = strlen(temp) + 1; + author = malloc(length); + snprintf(author, length, "%s", temp); + } + } json_object_put(tweet_obj); - return (char *) author; + return author; } int -twitc_reply_has_error(void *contents) +twitc_reply_has_error(char *contents) { + int ret_has_errors = 0; json_object *json_obj; - json_object *errors_obj; - json_bool has_errors; - json_obj = json_tokener_parse((char *) contents); - has_errors = json_object_object_get_ex(json_obj, TWITC_REPLY_ERRORS, &errors_obj); + json_obj = parse_wrapper(contents); - json_object_put(json_obj); - if (has_errors) { - return 1; + if (json_object_object_get_ex(json_obj, TWITC_REPLY_ERRORS, NULL)) { + ret_has_errors = 1; } else { - return 0; + ret_has_errors = 0; } + + json_object_put(json_obj); + + return ret_has_errors; } int @@ -51,7 +82,7 @@ twitc_parse_reply(char *reply, char **token, char **secret) rc = oauth_split_url_parameters(reply, &rv); qsort(rv, rc, sizeof(char *), oauth_cmpstringp); - if (rc >= 1) { + if (rc >= 2) { success = 0; /*printf("rv[0]: %s\n", rv[0]);*/ /*printf("rv[1]: %s\n", rv[1]);*/ @@ -75,7 +106,12 @@ twitc_parse_reply(char *reply, char **token, char **secret) } } - if (rv) { free(rv); } + if (rv) { + for (int i = 0; i < rc; i++) { + free(rv[i]); + } + free(rv); + } return success; } diff --git a/src/twitc_reply.h b/src/twitc_reply.h index 8f78fa3..c9b4d01 100644 --- a/src/twitc_reply.h +++ b/src/twitc_reply.h @@ -18,8 +18,9 @@ * * @param contents a char* is expected in json format * @return the author + * NOTE: caller will need to free returned value */ -char *twitc_parse_json_author(void *contents); +char * twitc_parse_json_author(char *contents); /** * Takes a reply from twitters authentication endpoints and @@ -40,6 +41,6 @@ int twitc_parse_reply(char *reply, char **token, char **secret); * @param contents a char* is expected in json format * @return 0 == success and 1 == failure. */ -int twitc_reply_has_error(void *contents); +int twitc_reply_has_error(char *contents); #endif