From 9fc9494ccdbc95552d2cc992df0d643f54f9c42f Mon Sep 17 00:00:00 2001 From: rmuxnet Date: Tue, 9 Jun 2026 01:04:50 +0200 Subject: [PATCH] refactor: split static/ into css/ js/ html/; add tests/test_json.c (15/15 pass) --- .gitignore | 1 + Makefile | 28 ++++++++------ http/http.c | 12 +++--- static/{ => css}/iptv.css | 0 static/{ => html}/footer.html | 0 static/{ => html}/header.html | 0 static/{ => js}/downloads.js | 0 static/{ => js}/iptv.js | 0 static/{ => js}/series_show.js | 0 tests/test_json.c | 67 +++++++++++++++++++++++++++++++++ tests/test_runner | Bin 0 -> 21352 bytes 11 files changed, 91 insertions(+), 17 deletions(-) rename static/{ => css}/iptv.css (100%) rename static/{ => html}/footer.html (100%) rename static/{ => html}/header.html (100%) rename static/{ => js}/downloads.js (100%) rename static/{ => js}/iptv.js (100%) rename static/{ => js}/series_show.js (100%) create mode 100644 tests/test_json.c create mode 100755 tests/test_runner diff --git a/.gitignore b/.gitignore index c8737f7..7b2e3b6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.o iptv-dl +tests/test_runner diff --git a/Makefile b/Makefile index f6cd75d..764850b 100644 --- a/Makefile +++ b/Makefile @@ -34,20 +34,26 @@ $(TARGET): $(OBJS) $(CC) $(CFLAGS) -c $< -o $@ install: $(TARGET) - install -d $(BINDIR) $(SHAREDIR) $(SYSCONFDIR) - install -m 755 $(TARGET) $(BINDIR)/iptv-dl - install -m 644 static/iptv.css $(SHAREDIR)/iptv.css - install -m 644 static/iptv.js $(SHAREDIR)/iptv.js - install -m 644 static/downloads.js $(SHAREDIR)/downloads.js - install -m 644 static/series_show.js $(SHAREDIR)/series_show.js - install -m 644 static/header.html $(SHAREDIR)/header.html - install -m 644 static/footer.html $(SHAREDIR)/footer.html + install -d $(BINDIR) $(SHAREDIR)/css $(SHAREDIR)/js $(SHAREDIR)/html $(SYSCONFDIR) + install -m 755 $(TARGET) $(BINDIR)/iptv-dl + install -m 644 static/css/iptv.css $(SHAREDIR)/css/iptv.css + install -m 644 static/js/iptv.js $(SHAREDIR)/js/iptv.js + install -m 644 static/js/downloads.js $(SHAREDIR)/js/downloads.js + install -m 644 static/js/series_show.js $(SHAREDIR)/js/series_show.js + install -m 644 static/html/header.html $(SHAREDIR)/html/header.html + install -m 644 static/html/footer.html $(SHAREDIR)/html/footer.html @echo "" @echo "Installed to $(BINDIR)/iptv-dl" @echo "Create config at ~/.iptv-downloader/config.json or $(SYSCONFDIR)/config.json" @echo "Run: iptv-dl --dump-config (to see active defaults)" -clean: - rm -f $(OBJS) $(TARGET) +test: tests/test_runner + ./tests/test_runner -.PHONY: all install clean +tests/test_runner: tests/test_json.c util/json.c util/buf.c + $(CC) $(CFLAGS) $^ -o $@ + +clean: + rm -f $(OBJS) $(TARGET) tests/test_runner + +.PHONY: all install clean test diff --git a/http/http.c b/http/http.c index 8a317cd..9c9a4b0 100644 --- a/http/http.c +++ b/http/http.c @@ -11,9 +11,9 @@ char *g_footer = NULL; int g_footer_len = 0; char *g_css = NULL; int g_css_len = 0; StaticJS g_js[] = { - { "/iptv.js", "iptv.js", NULL, 0 }, - { "/downloads.js", "downloads.js", NULL, 0 }, - { "/series_show.js", "series_show.js", NULL, 0 }, + { "/iptv.js", "js/iptv.js", NULL, 0 }, + { "/downloads.js", "js/downloads.js", NULL, 0 }, + { "/series_show.js", "js/series_show.js", NULL, 0 }, { NULL, NULL, NULL, 0 } }; @@ -31,11 +31,11 @@ static int load_file(const char *path, char **out, int *out_len) { int http_load_templates(void) { char path[512]; - snprintf(path, sizeof(path), "%s/header.html", g_cfg.template_dir); + snprintf(path, sizeof(path), "%s/html/header.html", g_cfg.template_dir); if (!load_file(path, &g_header, &g_header_len)) return 0; - snprintf(path, sizeof(path), "%s/footer.html", g_cfg.template_dir); + snprintf(path, sizeof(path), "%s/html/footer.html", g_cfg.template_dir); if (!load_file(path, &g_footer, &g_footer_len)) return 0; - snprintf(path, sizeof(path), "%s/iptv.css", g_cfg.template_dir); + snprintf(path, sizeof(path), "%s/css/iptv.css", g_cfg.template_dir); if (!load_file(path, &g_css, &g_css_len)) return 0; for (int i = 0; g_js[i].url_path; i++) { snprintf(path, sizeof(path), "%s/%s", g_cfg.template_dir, g_js[i].fs_name); diff --git a/static/iptv.css b/static/css/iptv.css similarity index 100% rename from static/iptv.css rename to static/css/iptv.css diff --git a/static/footer.html b/static/html/footer.html similarity index 100% rename from static/footer.html rename to static/html/footer.html diff --git a/static/header.html b/static/html/header.html similarity index 100% rename from static/header.html rename to static/html/header.html diff --git a/static/downloads.js b/static/js/downloads.js similarity index 100% rename from static/downloads.js rename to static/js/downloads.js diff --git a/static/iptv.js b/static/js/iptv.js similarity index 100% rename from static/iptv.js rename to static/js/iptv.js diff --git a/static/series_show.js b/static/js/series_show.js similarity index 100% rename from static/series_show.js rename to static/js/series_show.js diff --git a/tests/test_json.c b/tests/test_json.c new file mode 100644 index 0000000..07a3a76 --- /dev/null +++ b/tests/test_json.c @@ -0,0 +1,67 @@ +/* + * tests/test_json.c — unit tests for util/json.c + * Build: make test + * Run: ./test_runner + */ +#include +#include +#include +#include +#include "json.h" + +static int passed = 0, failed = 0; + +#define CHECK(expr, desc) do { \ + if (expr) { printf(" PASS: %s\n", desc); passed++; } \ + else { printf(" FAIL: %s\n", desc); failed++; } \ +} while(0) + +static void test_json_str(void) { + printf("json_str:\n"); + const char *j = "{\"name\":\"test show\",\"id\":\"42\",\"flag\":true}"; + char *v; + + v = json_str(j, "name"); CHECK(v && strcmp(v,"test show")==0, "string value"); free(v); + v = json_str(j, "id"); CHECK(v && strcmp(v,"42")==0, "numeric string"); free(v); + v = json_str(j, "flag"); CHECK(v && strcmp(v,"true")==0, "bare value"); free(v); + v = json_str(j, "missing"); CHECK(v == NULL, "missing key"); free(v); +} + +static void test_json_array(void) { + printf("json_array:\n"); + const char *j = "[{\"id\":\"1\"},{\"id\":\"2\"},{\"id\":\"3\"}]"; + int n; char **arr = json_array(j, &n); + CHECK(n == 3, "count == 3"); + if (arr) { + char *v = json_str(arr[0], "id"); CHECK(v && strcmp(v,"1")==0, "arr[0].id==1"); free(v); + v = json_str(arr[2], "id"); CHECK(v && strcmp(v,"3")==0, "arr[2].id==3"); free(v); + for (int i = 0; i < n; i++) free(arr[i]); + free(arr); + } + /* empty array */ + arr = json_array("[]", &n); CHECK(n == 0, "empty array n==0"); free(arr); +} + +static void test_urldecode(void) { + printf("urldecode:\n"); + char s1[] = "hello%20world"; urldecode(s1); CHECK(strcmp(s1,"hello world")==0, "%%20 → space"); + char s2[] = "a+b+c"; urldecode(s2); CHECK(strcmp(s2,"a b c")==0, "+ → space"); + char s3[] = "no%20change%21"; urldecode(s3); CHECK(strcmp(s3,"no change!")==0, "mixed"); +} + +static void test_str_icontains(void) { + printf("str_icontains:\n"); + CHECK(str_icontains("Walking Dead", "walking"), "case-insensitive match"); + CHECK(str_icontains("Walking Dead", "DEAD"), "uppercase needle"); + CHECK(!str_icontains("Walking Dead", "sopranos"),"no match"); + CHECK(str_icontains("anything", ""), "empty needle → true"); +} + +int main(void) { + test_json_str(); + test_json_array(); + test_urldecode(); + test_str_icontains(); + printf("\n%d passed, %d failed\n", passed, failed); + return failed ? 1 : 0; +} diff --git a/tests/test_runner b/tests/test_runner new file mode 100755 index 0000000000000000000000000000000000000000..b49370ebae3aba2e33006064679b61ba05269811 GIT binary patch literal 21352 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 z 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 F0szn8rgi`T literal 0 HcmV?d00001