#include "agent.h" #include #include #include #include #include static int failures = 0; static int checks = 0; #define CHECK(cond, msg) \ do { \ checks++; \ if (!(cond)) { \ failures++; \ fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, (msg)); \ } \ } while (0) #define CHECK_EQ_STR(a, b, msg) \ do { \ checks++; \ const char *_a = (a), *_b = (b); \ if (!_a || (_b && strcmp(_a, _b) != 0)) { \ failures++; \ fprintf(stderr, "FAIL %s:%d: %s (got `%s`, want `%s`)\n", \ __FILE__, __LINE__, (msg), _a ? _a : "(null)", \ _b ? _b : "(null)"); \ } \ } while (0) static config mkcfg(provider_kind p, const char *model, const char *base, const char *key) { config c; memset(&c, 0, sizeof c); c.provider = p; c.model = xstrdup(model); c.base_url = base ? xstrdup(base) : NULL; c.api_key = key ? xstrdup(key) : NULL; return c; } static void freecfg(config *c) { free(c->model); free(c->base_url); free(c->api_key); } static void test_extract_openai_context_length(void) { config c = mkcfg(PROV_OPENAI, "llm", NULL, NULL); cJSON *j = cJSON_Parse("{\"id\":\"llm\",\"object\":\"model\"," "\"context_length\":262144}"); CHECK(extract_context_limit(j, &c) == 262144, "openai top-level limit"); cJSON_Delete(j); j = cJSON_Parse("{\"id\":\"llm\",\"object\":\"model\"}"); CHECK(extract_context_limit(j, &c) == 0, "openai missing limit"); cJSON_Delete(j); j = cJSON_Parse("{\"object\":\"list\",\"data\":[" "{\"id\":\"llm\",\"aliases\":[\"llm\"]," "\"owned_by\":\"llamacpp\",\"meta\":{\"n_ctx\":131072}}," "{\"id\":\"other\",\"meta\":{\"n_ctx\":8192}}]}"); CHECK(extract_context_limit(j, &c) == 131072, "llama.cpp meta.n_ctx for matching model"); cJSON_Delete(j); freecfg(&c); } static void test_openai_model_present(void) { CHECK(openai_model_present("{\"data\":[{\"id\":\"llm\"},{\"id\":\"other\"}]}", "llm"), "id match in data[]"); CHECK(!openai_model_present("{\"data\":[{\"id\":\"llm\"},{\"id\":\"other\"}]}", "nope"), "id miss"); CHECK(openai_model_present( "{\"models\":[{\"name\":\"llm\"}]," "\"data\":[{\"id\":\"x\",\"aliases\":[\"llm\"]}]}", "llm"), "models[].name and data[].aliases variants"); CHECK(!openai_model_present("{\"data\":[]}", "llm"), "empty data[]"); CHECK(!openai_model_present("not json", "llm"), "malformed body"); /* parity: a non-array `data` blocks the `models` fallback (Rust * get("data").or_else(|| get("models"))) */ CHECK(!openai_model_present( "{\"data\":{\"id\":\"x\"},\"models\":[{\"id\":\"llm\"}]}", "llm"), "non-array data does not fall back to models"); } static void test_extract_ollama_context_length(void) { config c = mkcfg(PROV_OLLAMA, "llama3.2", NULL, NULL); cJSON *j = cJSON_Parse("{\"name\":\"llama3.2\"," "\"model_details\":{\"family\":\"llama\"," "\"context_length\":8192}," "\"modelfiles\":[{\"parameters\":" "{\"context_length\":4096}}]}"); CHECK(extract_context_limit(j, &c) == 8192, "ollama model_details"); cJSON_Delete(j); j = cJSON_Parse("{\"modelfiles\":[{\"parameters\":" "{\"context_length\":4096}}]}"); CHECK(extract_context_limit(j, &c) == 4096, "ollama modelfiles fallback"); cJSON_Delete(j); j = cJSON_Parse("{\"name\":\"llama3.2\",\"model_details\":{}}"); CHECK(extract_context_limit(j, &c) == 0, "ollama absent"); cJSON_Delete(j); freecfg(&c); } static void test_extract_anthropic_max_tokens(void) { cJSON *j = cJSON_Parse("{\"data\":[" "{\"id\":\"claude-haiku\",\"max_tokens\":8192}," "{\"id\":\"claude-sonnet-4-6\",\"max_tokens\":64000}]}"); config c = mkcfg(PROV_ANTHROPIC, "claude-sonnet-4-6", NULL, NULL); CHECK(extract_context_limit(j, &c) == 64000, "matching model wins"); freecfg(&c); c = mkcfg(PROV_ANTHROPIC, "unknown-model", NULL, NULL); CHECK(extract_context_limit(j, &c) == 8192, "unknown model falls back to the first entry"); freecfg(&c); cJSON_Delete(j); j = cJSON_Parse("{\"data\":[]}"); c = mkcfg(PROV_ANTHROPIC, "unknown-model", NULL, NULL); CHECK(extract_context_limit(j, &c) == 0, "empty data[] -> none"); cJSON_Delete(j); freecfg(&c); } static void test_anthropic_model_present(void) { const char *body = "{\"data\":[{\"id\":\"claude-haiku\"},{\"id\":\"claude-sonnet-4-6\"}]}"; CHECK(anthropic_model_present(body, "claude-sonnet-4-6"), "id match"); CHECK(!anthropic_model_present(body, "claude-unknown"), "id miss"); CHECK(!anthropic_model_present("not json", "claude-haiku"), "malformed body never present"); CHECK(!anthropic_model_present("{\"data\":[]}", "claude-haiku"), "empty list"); CHECK(!anthropic_model_present("{\"data\":{\"id\":\"x\"}}", "x"), "data not an array"); } static void test_probe_helpers(void) { provider_probe ok = {true, xstrdup("model `llm` reachable at http://x")}; CHECK(ok.ok, "ok flag"); CHECK_EQ_STR(ok.detail, "model `llm` reachable at http://x", "ok detail"); provider_probe_free(&ok); CHECK(ok.detail == NULL, "probe_free releases detail"); provider_probe bad = {false, xstrdup("could not reach http://x " "(connection: timeout)")}; CHECK(!bad.ok, "fail flag"); CHECK(strstr(bad.detail, "timeout") != NULL, "fail detail content"); provider_probe_free(&bad); CHECK(bad.detail == NULL, "probe_free releases failed detail"); } static void test_default_base_urls(void) { CHECK_EQ_STR(agent_default_base_url(PROV_OPENAI), "https://api.openai.com/v1", "openai default base"); CHECK_EQ_STR(agent_default_base_url(PROV_ANTHROPIC), "https://api.anthropic.com", "anthropic default base"); CHECK_EQ_STR(agent_default_base_url(PROV_OLLAMA), "http://localhost:11434", "ollama default base"); } static void test_models_url(void) { config c = mkcfg(PROV_OPENAI, "llm", NULL, NULL); char *u = agent_models_url(&c); CHECK_EQ_STR(u, "https://api.openai.com/v1/models", "openai default url"); free(u); freecfg(&c); c = mkcfg(PROV_OPENAI, "llm", "http://localhost:8080/v1/", "k"); u = agent_models_url(&c); CHECK_EQ_STR(u, "http://localhost:8080/v1/models", "custom base, trailing slash trimmed"); free(u); freecfg(&c); c = mkcfg(PROV_ANTHROPIC, "claude-sonnet-4-6", NULL, "k"); u = agent_models_url(&c); CHECK_EQ_STR(u, "https://api.anthropic.com/v1/models?limit=100", "anthropic url"); free(u); freecfg(&c); c = mkcfg(PROV_OLLAMA, "llama3.2", NULL, NULL); u = agent_models_url(&c); CHECK_EQ_STR(u, "http://localhost:11434/api/show?name=llama3.2", "ollama url"); free(u); freecfg(&c); c = mkcfg(PROV_OLLAMA, "llama 3.2", "http://host:1/x/", NULL); u = agent_models_url(&c); CHECK(strstr(u, "name=llama%203.2") != NULL, "ollama name is url-encoded"); free(u); freecfg(&c); } static void test_auth(void) { config c = mkcfg(PROV_OPENAI, "llm", NULL, "sk-1"); char *h = NULL, *v = NULL; agent_auth(&c, &h, &v); CHECK_EQ_STR(h, "Authorization", "openai auth header"); CHECK_EQ_STR(v, "Bearer sk-1", "openai bearer value"); free(h); free(v); freecfg(&c); c = mkcfg(PROV_OPENAI, "llm", "http://x", NULL); agent_auth(&c, &h, &v); CHECK(h == NULL && v == NULL, "openai without key: no auth"); free(h); free(v); freecfg(&c); c = mkcfg(PROV_ANTHROPIC, "m", NULL, "ak"); agent_auth(&c, &h, &v); CHECK_EQ_STR(h, "x-api-key", "anthropic auth header"); CHECK_EQ_STR(v, "ak", "anthropic key passthrough"); free(h); free(v); freecfg(&c); c = mkcfg(PROV_OLLAMA, "m", NULL, "ignored"); agent_auth(&c, &h, &v); CHECK(h == NULL && v == NULL, "ollama sends no auth"); free(h); free(v); freecfg(&c); } static void test_http_get_error_path(void) { long status = 0; char *body = NULL, *err = NULL; /* port 1 on localhost: connection refused, no server expected */ int rc = agent_http_get("http://127.0.0.1:1/models", NULL, NULL, &status, &body, &err); CHECK(rc == -1, "unreachable endpoint -> transport failure"); CHECK(err != NULL, "transport error message present"); CHECK(body == NULL, "no body on transport failure"); free(err); free(body); } int main(void) { curl_global_init(CURL_GLOBAL_DEFAULT); test_extract_openai_context_length(); test_openai_model_present(); test_extract_ollama_context_length(); test_extract_anthropic_max_tokens(); test_anthropic_model_present(); test_probe_helpers(); test_default_base_urls(); test_models_url(); test_auth(); test_http_get_error_path(); curl_global_cleanup(); printf("test_agent: %d checks, %d failures\n", checks, failures); return failures ? 1 : 0; }