summaryrefslogtreecommitdiffstats
path: root/tests/test_cepis.c
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_cepis.c')
-rw-r--r--tests/test_cepis.c721
1 files changed, 721 insertions, 0 deletions
diff --git a/tests/test_cepis.c b/tests/test_cepis.c
new file mode 100644
index 0000000..3dd6dc6
--- /dev/null
+++ b/tests/test_cepis.c
@@ -0,0 +1,721 @@
+/*
+ * Test driver. Every case runs against the local server in tests/server.py,
+ * reached through base_url; nothing here mocks the HTTP layer.
+ *
+ * Usage: test_cepis http://127.0.0.1:<port>
+ */
+
+#define _POSIX_C_SOURCE 200809L
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "cepis.h"
+
+_Static_assert(sizeof(cepis_geo) == 32, "cepis_geo layout");
+_Static_assert(offsetof(cepis_geo, precisao) == 24, "cepis_geo layout");
+_Static_assert(sizeof(cepis_address) == 120, "cepis_address layout");
+_Static_assert(offsetof(cepis_address, siafi) == 96, "cepis_address layout");
+_Static_assert(offsetof(cepis_address, geo) == 104, "cepis_address layout");
+_Static_assert(offsetof(cepis_address, raw) == 112, "cepis_address layout");
+_Static_assert(sizeof(struct cepis_health) == 48, "cepis_health layout");
+_Static_assert(offsetof(struct cepis_health, base_gerada_em) == 32,
+ "cepis_health layout");
+_Static_assert(sizeof(cepis_error) == 272, "cepis_error layout");
+_Static_assert(offsetof(cepis_error, retry_after) == 8, "cepis_error layout");
+_Static_assert(offsetof(cepis_error, message) == 16, "cepis_error layout");
+_Static_assert(sizeof(cepis_config) == 56, "cepis_config layout");
+_Static_assert(offsetof(cepis_config, max_attempts) == 16,
+ "cepis_config layout");
+_Static_assert(offsetof(cepis_config, user_agent) == 48, "cepis_config layout");
+
+static const char *base;
+static int failures;
+static int checks;
+static const char *current;
+
+#define CHECK(cond) \
+ do { \
+ checks++; \
+ if (!(cond)) { \
+ failures++; \
+ fprintf(stderr, " FAIL %s:%d [%s]: %s\n", __FILE__, __LINE__, \
+ current, #cond); \
+ } \
+ } while (0)
+
+#define CHECK_STR(got, want) \
+ do { \
+ checks++; \
+ const char *g_ = (got); \
+ if (g_ == NULL || strcmp(g_, (want)) != 0) { \
+ failures++; \
+ fprintf(stderr, " FAIL %s:%d [%s]: want \"%s\" got \"%s\"\n", \
+ __FILE__, __LINE__, current, (want), \
+ g_ ? g_ : "(null)"); \
+ } \
+ } while (0)
+
+static void begin(const char *name)
+{
+ current = name;
+ printf("- %s\n", name);
+}
+
+static char *urlf(const char *fmt, ...)
+{
+ static char buf[8][512];
+ static int slot;
+ char *out = buf[slot++ % 8];
+ va_list ap;
+ va_start(ap, fmt);
+ vsnprintf(out, 512, fmt, ap);
+ va_end(ap);
+ return out;
+}
+
+static cepis_client *client_at(const char *url, int attempts, double backoff)
+{
+ cepis_config cfg;
+ cepis_client *client = NULL;
+
+ memset(&cfg, 0, sizeof cfg);
+ cfg.base_url = url;
+ cfg.max_attempts = attempts;
+ cfg.backoff_base = backoff;
+ cfg.backoff_max = backoff * 4.0;
+ cfg.timeout = 5.0;
+ if (cepis_client_new(&cfg, &client) != CEPIS_OK)
+ return NULL;
+ return client;
+}
+
+static long hits(const char *prefix)
+{
+ cepis_client *client;
+
+ client = client_at(urlf("%s/_stats/%s", base, prefix), 1, 0.001);
+ struct cepis_health *health = NULL;
+ long count = -1;
+
+ if (client == NULL)
+ return -1;
+ if (cepis_health(client, &health, NULL) == CEPIS_OK && health != NULL)
+ count = (long)health->ceps;
+ cepis_health_free(health);
+ cepis_client_free(client);
+ return count;
+}
+
+static double now_seconds(void)
+{
+ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ return (double)ts.tv_sec + (double)ts.tv_nsec / 1e9;
+}
+
+static void test_normalize(void)
+{
+ char out[16];
+
+ begin("normalize_cep");
+ CHECK(cepis_normalize_cep("01001-000", out) == CEPIS_OK);
+ CHECK_STR(out, "01001000");
+ CHECK(cepis_normalize_cep("01001000", out) == CEPIS_OK);
+ CHECK_STR(out, "01001000");
+ CHECK(cepis_normalize_cep(" 01001-000 ", out) == CEPIS_OK);
+ CHECK_STR(out, "01001000");
+ CHECK(cepis_normalize_cep("01.001-000", out) == CEPIS_OK);
+ CHECK_STR(out, "01001000");
+ CHECK(cepis_normalize_cep("0100100", out) == CEPIS_ERR_INVALID_CEP);
+ CHECK_STR(out, "");
+ CHECK(cepis_normalize_cep("010010000", out) == CEPIS_ERR_INVALID_CEP);
+ CHECK(cepis_normalize_cep("0100100a", out) == CEPIS_ERR_INVALID_CEP);
+ CHECK(cepis_normalize_cep("", out) == CEPIS_ERR_INVALID_CEP);
+ CHECK(cepis_normalize_cep(NULL, out) == CEPIS_ERR_INVALID_CEP);
+ CHECK(cepis_normalize_cep("01001000", NULL) == CEPIS_ERR_USAGE);
+}
+
+static void test_lookup_geo(void)
+{
+ cepis_client *client = client_at(base, 3, 0.001);
+ cepis_address *address = NULL;
+ cepis_error err;
+ char masked[16];
+
+ begin("lookup with geo");
+ CHECK(cepis_lookup(client, "01001-000", &address, &err) == CEPIS_OK);
+ if (address != NULL) {
+ CHECK_STR(address->cep, "01001000");
+ CHECK_STR(address->logradouro, "Praça da Sé");
+ CHECK_STR(address->complemento, "lado ímpar");
+ CHECK_STR(address->unidade, "");
+ CHECK_STR(address->bairro, "Sé");
+ CHECK_STR(address->localidade, "São Paulo");
+ CHECK_STR(address->uf, "SP");
+ CHECK_STR(address->estado, "São Paulo");
+ CHECK_STR(address->regiao, "Sudeste");
+ CHECK_STR(address->ibge, "3550308");
+ CHECK_STR(address->gia, "1004");
+ CHECK_STR(address->ddd, "11");
+ CHECK_STR(address->siafi, "7107");
+ CHECK(address->raw != NULL);
+ CHECK(strstr(address->raw, "logradouro") != NULL);
+ CHECK(address->geo != NULL);
+ if (address->geo != NULL) {
+ CHECK(address->geo->lat < -23.5 && address->geo->lat > -23.6);
+ CHECK(address->geo->lon < -46.6 && address->geo->lon > -46.7);
+ CHECK(address->geo->raio_m == 1084);
+ CHECK_STR(address->geo->precisao, "logradouro");
+ CHECK(cepis_geo_is_precise(address->geo) == 1);
+ }
+ cepis_address_formatted_cep(address, masked);
+ CHECK_STR(masked, "01001-000");
+ }
+ cepis_address_free(address);
+
+ address = NULL;
+ CHECK(cepis_lookup(client, "69900000", &address, &err) == CEPIS_OK);
+ if (address != NULL) {
+ CHECK(address->geo != NULL);
+ if (address->geo != NULL) {
+ CHECK_STR(address->geo->precisao, "localidade");
+ CHECK(cepis_geo_is_precise(address->geo) == 0);
+ CHECK(address->geo->raio_m == 18420);
+ }
+ }
+ cepis_address_free(address);
+ cepis_client_free(client);
+}
+
+static void test_lookup_no_geo(void)
+{
+ cepis_client *client = client_at(base, 3, 0.001);
+ cepis_address *address = NULL;
+
+ begin("lookup without geo");
+ CHECK(cepis_lookup(client, "20040020", &address, NULL) == CEPIS_OK);
+ if (address != NULL) {
+ CHECK(address->geo == NULL);
+ CHECK_STR(address->localidade, "Rio de Janeiro");
+ CHECK_STR(address->gia, "");
+ }
+ cepis_address_free(address);
+ cepis_client_free(client);
+}
+
+static void test_escaped_json(void)
+{
+ cepis_client *client = client_at(urlf("%s/escaped", base), 1, 0.001);
+ cepis_address *address = NULL;
+
+ begin("\\u escapes decoded to UTF-8");
+ CHECK(cepis_lookup(client, "01001000", &address, NULL) == CEPIS_OK);
+ if (address != NULL) {
+ CHECK_STR(address->logradouro, "Praça da Sé");
+ CHECK_STR(address->localidade, "São Paulo");
+ }
+ cepis_address_free(address);
+ cepis_client_free(client);
+}
+
+static void test_surrogate_pair(void)
+{
+ cepis_client *client = client_at(urlf("%s/surrogate", base), 1, 0.001);
+ cepis_address *address = NULL;
+
+ begin("a surrogate pair decodes to one UTF-8 sequence");
+ CHECK(cepis_lookup(client, "01001000", &address, NULL) == CEPIS_OK);
+ if (address != NULL)
+ CHECK_STR(address->logradouro, "Rua \xf0\x9f\x98\x80 Feliz");
+ cepis_address_free(address);
+ cepis_client_free(client);
+}
+
+static void test_csv_bom(void)
+{
+ const char *ceps[] = {"01001000", "20040020"};
+ cepis_client *client = client_at(urlf("%s/bom", base), 1, 0.001);
+ cepis_address **out = NULL;
+
+ begin("a UTF-8 BOM on the CSV is stripped");
+ CHECK(cepis_lookup_many(client, ceps, 2, &out, NULL) == CEPIS_OK);
+ if (out != NULL) {
+ CHECK(out[0] != NULL);
+ CHECK(out[1] != NULL);
+ if (out[0] != NULL)
+ CHECK_STR(out[0]->cep, "01001000");
+ }
+ cepis_address_list_free(out, 2);
+ cepis_client_free(client);
+}
+
+static void test_not_found_not_retried(void)
+{
+ cepis_client *client = client_at(urlf("%s/nf", base), 3, 0.001);
+ cepis_address *address = NULL;
+ cepis_error err;
+
+ begin("404 is not retried");
+ CHECK(cepis_lookup(client, "99999999", &address, &err) ==
+ CEPIS_ERR_NOT_FOUND);
+ CHECK(address == NULL);
+ CHECK(err.status == CEPIS_ERR_NOT_FOUND);
+ CHECK(strstr(err.message, "99999999") != NULL);
+ CHECK(hits("nf") == 1);
+ cepis_client_free(client);
+}
+
+static void test_bad_cep_from_api(void)
+{
+ cepis_client *client = client_at(urlf("%s/always400", base), 3, 0.001);
+ cepis_address *address = NULL;
+ cepis_error err;
+
+ begin("400 is not retried and carries the API message");
+ CHECK(cepis_lookup(client, "00000000", &address, &err) ==
+ CEPIS_ERR_INVALID_CEP);
+ CHECK(address == NULL);
+ CHECK(strstr(err.message, "8 d") != NULL);
+ CHECK(hits("always400") == 1);
+ cepis_address_free(address);
+ cepis_client_free(client);
+}
+
+static void test_local_validation_makes_no_request(void)
+{
+ cepis_client *client = client_at(urlf("%s/novalid", base), 3, 0.001);
+ cepis_address *address = NULL;
+ cepis_error err;
+
+ begin("local validation makes no request");
+ CHECK(cepis_lookup(client, "123", &address, &err) == CEPIS_ERR_INVALID_CEP);
+ CHECK(address == NULL);
+ CHECK(strstr(err.message, "8 digits") != NULL);
+ CHECK(hits("novalid") == 0);
+ cepis_client_free(client);
+}
+
+static void test_retry_then_success(void)
+{
+ cepis_client *client = client_at(urlf("%s/fail2", base), 3, 0.001);
+ cepis_address *address = NULL;
+ cepis_error err;
+
+ begin("retry then success");
+ CHECK(cepis_lookup(client, "01001000", &address, &err) == CEPIS_OK);
+ CHECK(address != NULL);
+ CHECK(hits("fail2") == 3);
+ cepis_address_free(address);
+ cepis_client_free(client);
+}
+
+static void test_retry_exhausted(void)
+{
+ cepis_client *client = client_at(urlf("%s/always503", base), 2, 0.001);
+ cepis_address *address = NULL;
+ cepis_error err;
+
+ begin("retry exhausted on 5xx");
+ CHECK(cepis_lookup(client, "01001000", &address, &err) == CEPIS_ERR_SERVER);
+ CHECK(address == NULL);
+ CHECK(err.status == CEPIS_ERR_SERVER);
+ CHECK(err.http_status == 503);
+ CHECK(strstr(err.message, "503") != NULL);
+ CHECK(hits("always503") == 2);
+ cepis_client_free(client);
+}
+
+static void test_rate_limited(void)
+{
+ cepis_client *client = client_at(urlf("%s/always429", base), 2, 0.001);
+ cepis_address *address = NULL;
+ cepis_error err;
+
+ begin("429 exposes Retry-After in seconds");
+ CHECK(cepis_lookup(client, "01001000", &address, &err) ==
+ CEPIS_ERR_RATE_LIMITED);
+ CHECK(address == NULL);
+ CHECK(err.http_status == 429);
+ CHECK(err.retry_after > 0.5 && err.retry_after < 1.5);
+ CHECK(hits("always429") == 2);
+ cepis_client_free(client);
+}
+
+static void test_rate_limited_http_date(void)
+{
+ cepis_client *client = client_at(urlf("%s/date429", base), 2, 0.001);
+ cepis_address *address = NULL;
+ cepis_error err;
+
+ begin("429 with an HTTP-date Retry-After");
+ CHECK(cepis_lookup(client, "01001000", &address, &err) ==
+ CEPIS_ERR_RATE_LIMITED);
+ CHECK(err.retry_after > 0.0 && err.retry_after <= 3.0);
+ CHECK(hits("date429") == 2);
+ cepis_client_free(client);
+}
+
+static void test_retry_after_is_capped(void)
+{
+ cepis_client *client = client_at(urlf("%s/hugeretry", base), 2, 0.01);
+ cepis_address *address = NULL;
+ cepis_error err;
+ double started = now_seconds();
+
+ begin("a huge Retry-After is capped at backoff_max");
+ CHECK(cepis_lookup(client, "01001000", &address, &err) ==
+ CEPIS_ERR_RATE_LIMITED);
+ CHECK(now_seconds() - started < 5.0);
+ CHECK(err.retry_after > 3000.0);
+ cepis_client_free(client);
+}
+
+static void test_total_timeout(void)
+{
+ cepis_config cfg;
+ cepis_client *client = NULL;
+ cepis_address *address = NULL;
+ cepis_error err;
+ double started;
+
+ begin("total_timeout bounds the whole call");
+ memset(&cfg, 0, sizeof cfg);
+ cfg.base_url = urlf("%s/always503", base);
+ cfg.max_attempts = 50;
+ cfg.backoff_base = 0.05;
+ cfg.backoff_max = 0.05;
+ cfg.total_timeout = 0.4;
+ CHECK(cepis_client_new(&cfg, &client) == CEPIS_OK);
+ started = now_seconds();
+ CHECK(cepis_lookup(client, "01001000", &address, &err) == CEPIS_ERR_SERVER);
+ CHECK(now_seconds() - started < 4.0);
+ cepis_client_free(client);
+}
+
+static void test_invalid_response(void)
+{
+ cepis_client *client = client_at(urlf("%s/badjson", base), 1, 0.001);
+ cepis_address *address = NULL;
+ cepis_error err;
+
+ begin("a 200 with an unparseable body");
+ CHECK(cepis_lookup(client, "01001000", &address, &err) ==
+ CEPIS_ERR_INVALID_RESPONSE);
+ CHECK(address == NULL);
+ cepis_client_free(client);
+}
+
+static void test_bad_geo_kept_address(void)
+{
+ cepis_client *client = client_at(urlf("%s/badgeo", base), 1, 0.001);
+ cepis_address *address = NULL;
+
+ begin("a malformed geo drops the geo, not the address");
+ CHECK(cepis_lookup(client, "01001000", &address, NULL) == CEPIS_OK);
+ if (address != NULL) {
+ CHECK(address->geo == NULL);
+ CHECK_STR(address->localidade, "São Paulo");
+ CHECK(strstr(address->raw, "campo_novo") != NULL);
+ }
+ cepis_address_free(address);
+ cepis_client_free(client);
+}
+
+static void test_network_error(void)
+{
+ cepis_client *client = client_at("http://127.0.0.1:1", 2, 0.001);
+ cepis_address *address = NULL;
+ cepis_error err;
+
+ begin("a refused connection is a network error");
+ CHECK(cepis_lookup(client, "01001000", &address, &err) ==
+ CEPIS_ERR_NETWORK);
+ CHECK(address == NULL);
+ CHECK(err.message[0] != '\0');
+ cepis_client_free(client);
+}
+
+static void test_health(void)
+{
+ cepis_client *client = client_at(base, 2, 0.001);
+ struct cepis_health *health = NULL;
+
+ begin("health");
+ CHECK(cepis_health(client, &health, NULL) == CEPIS_OK);
+ if (health != NULL) {
+ CHECK_STR(health->status, "ok");
+ CHECK_STR(health->dne, "26071");
+ CHECK(health->ceps == 1596168);
+ CHECK(health->geo == 894489);
+ CHECK_STR(health->base_gerada_em, "2026-07-24T03:48:28Z");
+ CHECK(health->raw != NULL);
+ }
+ cepis_health_free(health);
+ cepis_client_free(client);
+}
+
+static void test_health_degraded(void)
+{
+ cepis_client *client = client_at(urlf("%s/degraded", base), 2, 0.001);
+ struct cepis_health *health = NULL;
+
+ begin("a degraded API is not an error");
+ CHECK(cepis_health(client, &health, NULL) == CEPIS_OK);
+ if (health != NULL)
+ CHECK_STR(health->status, "degraded");
+ CHECK(hits("degraded") == 1);
+ cepis_health_free(health);
+ cepis_client_free(client);
+}
+
+static void test_batch_order(void)
+{
+ const char *ceps[] = {"01001000", "99999999", "20040020"};
+ cepis_client *client = client_at(urlf("%s/batch", base), 2, 0.001);
+ cepis_address **out = NULL;
+
+ begin("batch keeps order and nulls the missing entry");
+ CHECK(cepis_lookup_many(client, ceps, 3, &out, NULL) == CEPIS_OK);
+ if (out != NULL) {
+ CHECK(out[0] != NULL);
+ CHECK(out[1] == NULL);
+ CHECK(out[2] != NULL);
+ if (out[0] != NULL) {
+ CHECK_STR(out[0]->localidade, "São Paulo");
+ CHECK(out[0]->geo == NULL);
+ CHECK(out[0]->raw == NULL);
+ }
+ if (out[2] != NULL)
+ CHECK_STR(out[2]->localidade, "Rio de Janeiro");
+ }
+ CHECK(hits("batch") == 1);
+ cepis_address_list_free(out, 3);
+ cepis_client_free(client);
+}
+
+static void test_batch_quoting(void)
+{
+ const char *ceps[] = {"12345678"};
+ cepis_client *client = client_at(base, 2, 0.001);
+ cepis_address **out = NULL;
+
+ begin("batch parses quoted CSV fields");
+ CHECK(cepis_lookup_many(client, ceps, 1, &out, NULL) == CEPIS_OK);
+ if (out != NULL && out[0] != NULL) {
+ CHECK_STR(out[0]->logradouro, "Rua A, B \"quoted\"");
+ CHECK_STR(out[0]->bairro, "Vila, Nova");
+ CHECK_STR(out[0]->complemento, "até 100");
+ }
+ cepis_address_list_free(out, 1);
+ cepis_client_free(client);
+}
+
+static void test_batch_malformed_entry(void)
+{
+ const char *ceps[] = {"01001000", "abc", "20040020"};
+ cepis_client *client = client_at(base, 2, 0.001);
+ cepis_address **out = NULL;
+
+ begin("a malformed CEP in a batch is null, not an error");
+ CHECK(cepis_lookup_many(client, ceps, 3, &out, NULL) == CEPIS_OK);
+ if (out != NULL) {
+ CHECK(out[0] != NULL);
+ CHECK(out[1] == NULL);
+ CHECK(out[2] != NULL);
+ }
+ cepis_address_list_free(out, 3);
+ cepis_client_free(client);
+}
+
+static void test_batch_chunking(void)
+{
+ cepis_client *client = client_at(urlf("%s/chunk", base), 2, 0.001);
+ cepis_address **out = NULL;
+ const char **ceps;
+ char *storage;
+ size_t i;
+ size_t n = 501;
+ int ok = 1;
+
+ begin("batches above 500 are chunked");
+ ceps = malloc(n * sizeof *ceps);
+ storage = malloc(n * 9);
+ for (i = 0; i < n; i++) {
+ snprintf(storage + i * 9, 9, "%08zu", (size_t)70000000 + i);
+ ceps[i] = storage + i * 9;
+ }
+ CHECK(cepis_lookup_many(client, ceps, n, &out, NULL) == CEPIS_OK);
+ if (out != NULL) {
+ for (i = 0; i < n; i++) {
+ if (out[i] == NULL || strcmp(out[i]->cep, ceps[i]) != 0) {
+ ok = 0;
+ break;
+ }
+ }
+ CHECK(ok);
+ }
+ CHECK(hits("chunk") == 2);
+ cepis_address_list_free(out, n);
+ free(storage);
+ free(ceps);
+ cepis_client_free(client);
+}
+
+static void test_batch_short_response(void)
+{
+ const char *ceps[] = {"01001000", "20040020", "12345678"};
+ cepis_client *client = client_at(urlf("%s/short", base), 2, 0.001);
+ cepis_address **out = NULL;
+
+ begin("a short batch response is padded, not shifted");
+ CHECK(cepis_lookup_many(client, ceps, 3, &out, NULL) == CEPIS_OK);
+ if (out != NULL) {
+ CHECK(out[0] != NULL);
+ CHECK(out[1] == NULL);
+ CHECK(out[2] == NULL);
+ if (out[0] != NULL)
+ CHECK_STR(out[0]->cep, "01001000");
+ }
+ cepis_address_list_free(out, 3);
+ cepis_client_free(client);
+}
+
+static void test_batch_empty(void)
+{
+ cepis_client *client = client_at(base, 2, 0.001);
+ cepis_address **out = NULL;
+
+ begin("an empty batch is a valid empty result");
+ CHECK(cepis_lookup_many(client, NULL, 0, &out, NULL) == CEPIS_OK);
+ CHECK(out != NULL);
+ cepis_address_list_free(out, 0);
+ cepis_client_free(client);
+}
+
+static void test_trailing_slash(void)
+{
+ cepis_client *client = client_at(urlf("%s/", base), 2, 0.001);
+ cepis_address *address = NULL;
+
+ begin("a trailing slash in base_url is stripped");
+ CHECK(cepis_lookup(client, "01001000", &address, NULL) == CEPIS_OK);
+ CHECK(address != NULL);
+ cepis_address_free(address);
+ cepis_client_free(client);
+}
+
+int cepis_abi_probe(void *fn, void *a0, void *a1, void *a2, void *a3);
+
+static void probe(const char *name, void *fn, void *a0, void *a1, void *a2,
+ void *a3)
+{
+ int mask = cepis_abi_probe(fn, a0, a1, a2, a3);
+
+ checks++;
+ if (mask != 0) {
+ failures++;
+ fprintf(stderr, " FAIL [%s]: %s violated the ABI, mask 0x%02x\n",
+ current, name, mask);
+ }
+}
+
+static void test_abi(void)
+{
+ cepis_client *client = client_at(base, 2, 0.001);
+ cepis_address *address = NULL;
+ struct cepis_health *health = NULL;
+ cepis_address **many = NULL;
+ cepis_error err;
+ char out[16];
+
+ begin("callee-saved registers, stack pointer and DF survive every call");
+ probe("cepis_normalize_cep", (void *)cepis_normalize_cep,
+ (void *)"01001-000", out, NULL, NULL);
+ probe("cepis_strerror", (void *)cepis_strerror, (void *)(long)4, NULL,
+ NULL, NULL);
+ probe("cepis_lookup(invalid)", (void *)cepis_lookup, client, (void *)"1",
+ &address, &err);
+ probe("cepis_lookup", (void *)cepis_lookup, client, (void *)"01001000",
+ &address, &err);
+ probe("cepis_address_free", (void *)cepis_address_free, address, NULL,
+ NULL, NULL);
+ probe("cepis_health", (void *)cepis_health, client, &health, &err, NULL);
+ probe("cepis_health_free", (void *)cepis_health_free, health, NULL, NULL,
+ NULL);
+ probe("cepis_lookup_many", (void *)cepis_lookup_many, client, NULL, NULL,
+ &many);
+ cepis_address_list_free(many, 0);
+ probe("cepis_default_client", (void *)cepis_default_client, NULL, NULL,
+ NULL, NULL);
+ probe("cepis_client_free", (void *)cepis_client_free, client, NULL, NULL,
+ NULL);
+}
+
+static void test_usage(void)
+{
+ cepis_client *client = client_at(base, 2, 0.001);
+ cepis_config cfg;
+ cepis_client *bad = NULL;
+
+ begin("argument errors");
+ CHECK(cepis_lookup(client, "01001000", NULL, NULL) == CEPIS_ERR_USAGE);
+ CHECK(cepis_health(client, NULL, NULL) == CEPIS_ERR_USAGE);
+ CHECK(cepis_lookup_many(client, NULL, 0, NULL, NULL) == CEPIS_ERR_USAGE);
+ memset(&cfg, 0, sizeof cfg);
+ cfg.max_attempts = -1;
+ CHECK(cepis_client_new(&cfg, &bad) == CEPIS_ERR_USAGE);
+ CHECK(bad == NULL);
+ CHECK(strcmp(cepis_version(), CEPIS_VERSION) == 0);
+ CHECK(strcmp(cepis_strerror(CEPIS_ERR_NOT_FOUND), "CEP not found") == 0);
+ cepis_client_free(client);
+}
+
+int main(int argc, char **argv)
+{
+ if (argc < 2) {
+ fprintf(stderr, "usage: %s <base-url>\n", argv[0]);
+ return 2;
+ }
+ base = argv[1];
+ cepis_global_init();
+
+ test_normalize();
+ test_lookup_geo();
+ test_lookup_no_geo();
+ test_escaped_json();
+ test_surrogate_pair();
+ test_csv_bom();
+ test_not_found_not_retried();
+ test_bad_cep_from_api();
+ test_local_validation_makes_no_request();
+ test_retry_then_success();
+ test_retry_exhausted();
+ test_rate_limited();
+ test_rate_limited_http_date();
+ test_retry_after_is_capped();
+ test_total_timeout();
+ test_invalid_response();
+ test_bad_geo_kept_address();
+ test_network_error();
+ test_health();
+ test_health_degraded();
+ test_batch_order();
+ test_batch_quoting();
+ test_batch_malformed_entry();
+ test_batch_chunking();
+ test_batch_short_response();
+ test_batch_empty();
+ test_trailing_slash();
+ test_abi();
+ test_usage();
+
+ cepis_global_cleanup();
+
+ printf("\n%d checks, %d failures\n", checks, failures);
+ return failures == 0 ? 0 : 1;
+}