134 lines
5.0 KiB
C
134 lines
5.0 KiB
C
/* test_models.c - /models 响应体的解析
|
|
*
|
|
* 只测纯解析. 拉取 (GET {base_url}/models)要走网络, 由 e2e 第 52 段对着
|
|
* mock 覆盖, 那里还顺带验了交互式选择本身 (方向键、确认、取消).
|
|
*
|
|
* 为什么要容忍这么多种形状:OpenAI 兼容是个很大的家庭, /models 的返回花样
|
|
* 比文档里写的多 —— 见过裸数组、见过重复列同一模型、见过元素里没有 id.
|
|
* 解析器要么跳过, 要么去重, 但**不能**因此整个失败:列表里少一个模型远好过
|
|
* 一个都选不了.
|
|
*/
|
|
#include "models.h"
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
static int failures = 0;
|
|
static int checks = 0;
|
|
|
|
#define CHECK(cond, ...) \
|
|
do { \
|
|
checks++; \
|
|
if (!(cond)) { \
|
|
failures++; \
|
|
printf(" ✗ %s:%d: ", __FILE__, __LINE__); \
|
|
printf(__VA_ARGS__); \
|
|
printf("\n"); \
|
|
} \
|
|
} while (0)
|
|
|
|
static void test_standard(void)
|
|
{
|
|
puts("标准形状:{\"data\":[{\"id\":...}]}");
|
|
const char *json =
|
|
"{\"object\":\"list\",\"data\":["
|
|
"{\"id\":\"alpha\",\"object\":\"model\",\"owned_by\":\"x\"},"
|
|
"{\"id\":\"beta\",\"object\":\"model\"}"
|
|
"]}";
|
|
size_t n = 0;
|
|
char **l = models_parse(json, &n);
|
|
CHECK(l && n == 2, "应解析出 2 个, 实际 %zu", n);
|
|
if (l && n == 2) {
|
|
CHECK(strcmp(l[0], "alpha") == 0, "顺序应保持服务端给的 (第一个 alpha)");
|
|
CHECK(strcmp(l[1], "beta") == 0, "第二个 beta");
|
|
}
|
|
models_free(l, n);
|
|
}
|
|
|
|
static void test_dedupe_and_skip(void)
|
|
{
|
|
puts("重复项要去重, 缺 id 的要跳过");
|
|
const char *json =
|
|
"{\"data\":["
|
|
"{\"id\":\"a\"},{\"id\":\"a\"},{\"object\":\"model\"},"
|
|
"{\"id\":\"\"},{\"id\":\"b\"},{\"id\":42}"
|
|
"]}";
|
|
size_t n = 0;
|
|
char **l = models_parse(json, &n);
|
|
CHECK(l && n == 2, "去重 + 跳过后应剩 2 个, 实际 %zu", n);
|
|
if (l && n == 2) {
|
|
CHECK(strcmp(l[0], "a") == 0, "第一个是 a");
|
|
CHECK(strcmp(l[1], "b") == 0, "第二个是 b");
|
|
}
|
|
models_free(l, n);
|
|
}
|
|
|
|
static void test_bare_array(void)
|
|
{
|
|
puts("兼容裸数组 (字符串 / 对象都认)");
|
|
size_t n = 0;
|
|
char **l = models_parse("[\"alpha\",\"beta\"]", &n);
|
|
CHECK(l && n == 2 && strcmp(l[1], "beta") == 0, "字符串数组应能解析");
|
|
models_free(l, n);
|
|
|
|
n = 0;
|
|
l = models_parse("[{\"id\":\"gamma\"}]", &n);
|
|
CHECK(l && n == 1 && strcmp(l[0], "gamma") == 0, "对象数组应能解析");
|
|
models_free(l, n);
|
|
}
|
|
|
|
static void test_bad_input(void)
|
|
{
|
|
puts("拿不到模型时返回 NULL, 而不是空数组或崩溃");
|
|
size_t n = 123;
|
|
CHECK(models_parse(NULL, &n) == NULL && n == 0, "NULL 输入");
|
|
n = 123;
|
|
CHECK(models_parse("", &n) == NULL && n == 0, "空串");
|
|
n = 123;
|
|
CHECK(models_parse("<html>not json</html>", &n) == NULL && n == 0,
|
|
"不是 JSON");
|
|
n = 123;
|
|
CHECK(models_parse("{\"data\":[]}", &n) == NULL && n == 0, "空列表");
|
|
n = 123;
|
|
CHECK(models_parse("{\"object\":\"list\"}", &n) == NULL && n == 0,
|
|
"没有 data 字段");
|
|
n = 123;
|
|
CHECK(models_parse("{\"data\":\"nope\"}", &n) == NULL && n == 0,
|
|
"data 不是数组");
|
|
n = 123;
|
|
CHECK(models_parse("{\"data\":[{\"object\":\"model\"}]}", &n) == NULL && n == 0,
|
|
"全是坏条目");
|
|
|
|
/* out_n 也可为 NULL (调用方只关心数组) */
|
|
char **l = models_parse("{\"data\":[{\"id\":\"x\"}]}", NULL);
|
|
CHECK(l != NULL, "out_n 为 NULL 也要能工作");
|
|
models_free(l, 1);
|
|
|
|
puts("空列表要与\"解析不了\"分开");
|
|
CHECK(models_is_empty_list("{\"object\":\"list\",\"data\":[]}") == 1,
|
|
"空的 data 应判为空列表");
|
|
CHECK(models_is_empty_list("[]") == 1, "空的裸数组也是空列表");
|
|
CHECK(models_is_empty_list("<html>not json</html>") == 0,
|
|
"解析不了不算空列表 (是两回事)");
|
|
CHECK(models_is_empty_list("{\"data\":\"nope\"}") == 0,
|
|
"data 不是数组不算空列表");
|
|
CHECK(models_is_empty_list("{\"data\":[{\"id\":\"a\"}]}") == 0,
|
|
"有模型就不是空列表");
|
|
CHECK(models_is_empty_list(NULL) == 0, "NULL 不算空列表");
|
|
CHECK(models_is_empty_list("") == 0, "空串不算空列表");
|
|
|
|
/* 释放 NULL 不该崩 */
|
|
models_free(NULL, 0);
|
|
CHECK(1, "释放 NULL 没崩");
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
test_standard();
|
|
test_dedupe_and_skip();
|
|
test_bare_array();
|
|
test_bad_input();
|
|
printf("\n%d 项检查, %d 项失败\n", checks, failures);
|
|
return failures ? 1 : 0;
|
|
}
|