| /****************************************************************************** |
| * Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. |
| * |
| * Test the HTTP router. |
| *****************************************************************************/ |
| |
| #define _GNU_SOURCE |
| #include <check.h> |
| |
| #include <stdint.h> |
| |
| #include "src/common/log.h" |
| #include "src/common/read_config.h" |
| #include "src/common/slurm_protocol_api.h" |
| |
| #include "src/common/http.h" |
| #include "src/common/http_con.h" |
| #include "src/common/http_router.h" |
| |
| /* |
| * Sentinels returned by the stub handlers. A single int return value then |
| * identifies which handler the router dispatched to. RC_NOT_FOUND and every |
| * route sentinel must differ from each other and from SLURM_SUCCESS (0). |
| */ |
| enum { |
| RC_NOT_FOUND = 1000, |
| RC_GENERIC, |
| RC_SUBMIT, |
| RC_JOB_ID, |
| RC_QOS_LIST, |
| RC_QOS_ONE, |
| }; |
| |
| /* |
| * Per-test observation state. Process-global, but each START_TEST runs in its |
| * own forked child (libcheck default CK_FORK), so it is effectively fresh per |
| * test; setup() also resets it. |
| */ |
| static struct { |
| int call_count; |
| int last_rc; /* sentinel of the last handler invoked */ |
| void *last_path_arg; /* path_arg the last route handler received */ |
| void *last_arg; /* arg passed to http_router_on_request() */ |
| } obs; |
| |
| /* Count of on_fini() callbacks observed during http_router_fini(). */ |
| static int fini_calls; |
| |
| static void reset_obs(void) |
| { |
| obs = (typeof(obs)) { 0 }; |
| } |
| |
| /* ---------------------------------------------------------------------- */ |
| /* Stub callbacks */ |
| /* ---------------------------------------------------------------------- */ |
| |
| static int stub_not_found(http_con_t *hcon, const char *name, |
| const http_con_request_t *request, void *arg, |
| void *path_arg) |
| { |
| obs.call_count++; |
| obs.last_arg = arg; |
| obs.last_path_arg = path_arg; /* router always passes NULL here */ |
| obs.last_rc = RC_NOT_FOUND; |
| return RC_NOT_FOUND; |
| } |
| |
| /* Generate a recorder that returns a fixed sentinel. hcon is never touched. */ |
| #define DEFINE_STUB(fn, sentinel) \ |
| static int fn(http_con_t *hcon, const char *name, \ |
| const http_con_request_t *request, void *arg, \ |
| void *path_arg) \ |
| { \ |
| obs.call_count++; \ |
| obs.last_arg = arg; \ |
| obs.last_path_arg = path_arg; \ |
| obs.last_rc = (sentinel); \ |
| return (sentinel); \ |
| } |
| |
| DEFINE_STUB(stub_generic, RC_GENERIC) |
| DEFINE_STUB(stub_submit, RC_SUBMIT) |
| DEFINE_STUB(stub_job_id, RC_JOB_ID) |
| DEFINE_STUB(stub_qos_list, RC_QOS_LIST) |
| DEFINE_STUB(stub_qos_one, RC_QOS_ONE) |
| |
| static void stub_fini(http_router_on_request_event_t on_request, void *path_arg) |
| { |
| fini_calls++; |
| } |
| |
| /* ---------------------------------------------------------------------- */ |
| /* Helpers */ |
| /* ---------------------------------------------------------------------- */ |
| |
| #define REQUEST_ARG ((void *) 0xA11C) |
| |
| /* Issue a request for (method, path) and return the router's rc. */ |
| static int do_request(http_request_method_t method, const char *path) |
| { |
| http_con_request_t request = { |
| .method = method, |
| .url = { |
| .path = (char *) path, |
| }, |
| }; |
| |
| return http_router_on_request(NULL, "test", &request, REQUEST_ARG); |
| } |
| |
| /* ---------------------------------------------------------------------- */ |
| /* Fixtures */ |
| /* ---------------------------------------------------------------------- */ |
| |
| static void setup(void) |
| { |
| log_options_t log_opts = LOG_OPTS_INITIALIZER; |
| const char *debug_env = getenv("SLURM_DEBUG"); |
| const char *debug_flags_env = getenv("SLURM_DEBUG_FLAGS"); |
| |
| if (debug_env) |
| log_opts.stderr_level = log_string2num(debug_env); |
| if (debug_flags_env) |
| debug_str2flags(debug_flags_env, &slurm_conf.debug_flags); |
| log_init("http-router-test", log_opts, 0, NULL); |
| |
| reset_obs(); |
| fini_calls = 0; |
| |
| /* |
| * Asserts on_not_found is set and that init is only called once per |
| * process. Safe here because each test runs in its own forked child. |
| */ |
| http_router_init(stub_not_found); |
| } |
| |
| static void teardown(void) |
| { |
| http_router_fini(); |
| log_fini(); |
| } |
| |
| /* ---------------------------------------------------------------------- */ |
| /* (a) Endpoint sweep */ |
| /* ---------------------------------------------------------------------- */ |
| |
| typedef struct { |
| http_request_method_t method; |
| const char *bind_path; |
| const char *request_path; |
| } endpoint_case_t; |
| |
| #define DP "v0.0.43" |
| #define SLURM_P "/slurm/" DP |
| #define DBD_P "/slurmdb/" DP |
| #define UTIL_P "/util/" DP |
| |
| /* Examples based on slurmrestd but they don't need to be sync'ed */ |
| static const endpoint_case_t endpoints[] = { |
| { HTTP_REQUEST_GET, SLURM_P "/shares", SLURM_P "/shares" }, |
| { HTTP_REQUEST_GET, SLURM_P "/reconfigure/", SLURM_P "/reconfigure" }, |
| { HTTP_REQUEST_GET, SLURM_P "/diag/", SLURM_P "/diag" }, |
| { HTTP_REQUEST_GET, SLURM_P "/ping/", SLURM_P "/ping" }, |
| { HTTP_REQUEST_GET, SLURM_P "/licenses/", SLURM_P "/licenses" }, |
| { HTTP_REQUEST_GET, SLURM_P "/jobs/", SLURM_P "/jobs" }, |
| { HTTP_REQUEST_GET, SLURM_P "/jobs/state/", SLURM_P "/jobs/state" }, |
| { HTTP_REQUEST_GET, SLURM_P "/job/{job_id}", SLURM_P "/job/123" }, |
| { HTTP_REQUEST_GET, SLURM_P "/job/{job_id}/requeue", |
| SLURM_P "/job/123/requeue" }, |
| { HTTP_REQUEST_GET, SLURM_P "/nodes/", SLURM_P "/nodes" }, |
| { HTTP_REQUEST_GET, SLURM_P "/node/{node_name}", SLURM_P "/node/n01" }, |
| { HTTP_REQUEST_GET, SLURM_P "/partitions/", SLURM_P "/partitions" }, |
| { HTTP_REQUEST_GET, SLURM_P "/partition/{partition_name}", |
| SLURM_P "/partition/debug" }, |
| { HTTP_REQUEST_GET, SLURM_P "/reservations/", SLURM_P "/reservations" }, |
| { HTTP_REQUEST_GET, SLURM_P "/reservation/{reservation_name}", |
| SLURM_P "/reservation/resv1" }, |
| { HTTP_REQUEST_GET, SLURM_P "/resources/{job_id}", |
| SLURM_P "/resources/123" }, |
| { HTTP_REQUEST_GET, SLURM_P "/conf", SLURM_P "/conf" }, |
| |
| { HTTP_REQUEST_POST, SLURM_P "/job/submit", SLURM_P "/job/submit" }, |
| { HTTP_REQUEST_POST, SLURM_P "/job/allocate", SLURM_P "/job/allocate" }, |
| { HTTP_REQUEST_POST, SLURM_P "/job/{job_id}", SLURM_P "/job/123" }, |
| { HTTP_REQUEST_POST, SLURM_P "/jobs/requeue", SLURM_P "/jobs/requeue" }, |
| { HTTP_REQUEST_POST, SLURM_P "/nodes/", SLURM_P "/nodes" }, |
| { HTTP_REQUEST_POST, SLURM_P "/node/{node_name}", SLURM_P "/node/n01" }, |
| { HTTP_REQUEST_POST, SLURM_P "/partitions/", SLURM_P "/partitions" }, |
| { HTTP_REQUEST_POST, SLURM_P "/reservations/", |
| SLURM_P "/reservations" }, |
| { HTTP_REQUEST_POST, SLURM_P "/reservation", SLURM_P "/reservation" }, |
| { HTTP_REQUEST_POST, SLURM_P "/new/node/", SLURM_P "/new/node" }, |
| |
| { HTTP_REQUEST_DELETE, SLURM_P "/jobs/", SLURM_P "/jobs" }, |
| { HTTP_REQUEST_DELETE, SLURM_P "/job/{job_id}", SLURM_P "/job/123" }, |
| { HTTP_REQUEST_DELETE, SLURM_P "/node/{node_name}", |
| SLURM_P "/node/n01" }, |
| { HTTP_REQUEST_DELETE, SLURM_P "/partition/{partition_name}", |
| SLURM_P "/partition/debug" }, |
| { HTTP_REQUEST_DELETE, SLURM_P "/reservation/{reservation_name}", |
| SLURM_P "/reservation/resv1" }, |
| |
| { HTTP_REQUEST_GET, DBD_P "/job/{job_id}", DBD_P "/job/123" }, |
| { HTTP_REQUEST_GET, DBD_P "/config", DBD_P "/config" }, |
| { HTTP_REQUEST_GET, DBD_P "/conf", DBD_P "/conf" }, |
| { HTTP_REQUEST_GET, DBD_P "/tres/", DBD_P "/tres" }, |
| { HTTP_REQUEST_GET, DBD_P "/qos/{qos}", DBD_P "/qos/normal" }, |
| { HTTP_REQUEST_GET, DBD_P "/qos/", DBD_P "/qos" }, |
| { HTTP_REQUEST_GET, DBD_P "/associations/", DBD_P "/associations" }, |
| { HTTP_REQUEST_GET, DBD_P "/association/", DBD_P "/association" }, |
| { HTTP_REQUEST_GET, DBD_P "/instances/", DBD_P "/instances" }, |
| { HTTP_REQUEST_GET, DBD_P "/instance/", DBD_P "/instance" }, |
| { HTTP_REQUEST_GET, DBD_P "/user/{name}", DBD_P "/user/root" }, |
| { HTTP_REQUEST_GET, DBD_P "/users/", DBD_P "/users" }, |
| { HTTP_REQUEST_GET, DBD_P "/cluster/{cluster_name}", |
| DBD_P "/cluster/cluster1" }, |
| { HTTP_REQUEST_GET, DBD_P "/clusters/", DBD_P "/clusters" }, |
| { HTTP_REQUEST_GET, DBD_P "/wckey/{id}", DBD_P "/wckey/5" }, |
| { HTTP_REQUEST_GET, DBD_P "/wckeys/", DBD_P "/wckeys" }, |
| { HTTP_REQUEST_GET, DBD_P "/account/{account_name}", |
| DBD_P "/account/acct1" }, |
| { HTTP_REQUEST_GET, DBD_P "/accounts/", DBD_P "/accounts" }, |
| { HTTP_REQUEST_GET, DBD_P "/jobs/", DBD_P "/jobs" }, |
| { HTTP_REQUEST_GET, DBD_P "/diag/", DBD_P "/diag" }, |
| { HTTP_REQUEST_GET, DBD_P "/ping/", DBD_P "/ping" }, |
| |
| { HTTP_REQUEST_POST, DBD_P "/job/{job_id}", DBD_P "/job/123" }, |
| { HTTP_REQUEST_POST, DBD_P "/config", DBD_P "/config" }, |
| { HTTP_REQUEST_POST, DBD_P "/tres/", DBD_P "/tres" }, |
| { HTTP_REQUEST_POST, DBD_P "/qos/", DBD_P "/qos" }, |
| { HTTP_REQUEST_POST, DBD_P "/associations/", DBD_P "/associations" }, |
| { HTTP_REQUEST_POST, DBD_P "/users_association/", |
| DBD_P "/users_association" }, |
| { HTTP_REQUEST_POST, DBD_P "/users/", DBD_P "/users" }, |
| { HTTP_REQUEST_POST, DBD_P "/clusters/", DBD_P "/clusters" }, |
| { HTTP_REQUEST_POST, DBD_P "/wckeys/", DBD_P "/wckeys" }, |
| { HTTP_REQUEST_POST, DBD_P "/accounts_association/", |
| DBD_P "/accounts_association" }, |
| { HTTP_REQUEST_POST, DBD_P "/accounts/", DBD_P "/accounts" }, |
| { HTTP_REQUEST_POST, DBD_P "/jobs/", DBD_P "/jobs" }, |
| |
| { HTTP_REQUEST_DELETE, DBD_P "/qos/{qos}", DBD_P "/qos/normal" }, |
| { HTTP_REQUEST_DELETE, DBD_P "/associations/", DBD_P "/associations" }, |
| { HTTP_REQUEST_DELETE, DBD_P "/association/", DBD_P "/association" }, |
| { HTTP_REQUEST_DELETE, DBD_P "/user/{name}", DBD_P "/user/root" }, |
| { HTTP_REQUEST_DELETE, DBD_P "/cluster/{cluster_name}", |
| DBD_P "/cluster/cluster1" }, |
| { HTTP_REQUEST_DELETE, DBD_P "/wckey/{id}", DBD_P "/wckey/5" }, |
| { HTTP_REQUEST_DELETE, DBD_P "/account/{account_name}", |
| DBD_P "/account/acct1" }, |
| |
| { HTTP_REQUEST_POST, UTIL_P "/hostnames", UTIL_P "/hostnames" }, |
| { HTTP_REQUEST_POST, UTIL_P "/hostlist", UTIL_P "/hostlist" }, |
| }; |
| |
| #define ENDPOINT_COUNT ARRAY_SIZE(endpoints) |
| |
| /* path_arg for endpoint i is (i + 1) so it is never NULL. */ |
| static void *endpoint_path_arg(int i) |
| { |
| return (void *) (intptr_t) (i + 1); |
| } |
| |
| static void bind_all_endpoints(void) |
| { |
| for (int i = 0; i < ENDPOINT_COUNT; i++) |
| http_router_bind(endpoints[i].method, endpoints[i].bind_path, |
| stub_generic, stub_fini, endpoint_path_arg(i)); |
| } |
| |
| START_TEST(test_endpoint_sweep) |
| { |
| int rc; |
| |
| /* Bind the full realistic tree so coexisting routes are exercised. */ |
| bind_all_endpoints(); |
| |
| rc = do_request(endpoints[_i].method, endpoints[_i].request_path); |
| |
| ck_assert_int_eq(rc, RC_GENERIC); |
| ck_assert_int_eq(obs.last_rc, RC_GENERIC); |
| ck_assert_ptr_eq(obs.last_path_arg, endpoint_path_arg(_i)); |
| } |
| |
| END_TEST |
| |
| /* ---------------------------------------------------------------------- */ |
| /* (b) RFC 3986 / HTTP method matrix */ |
| /* ---------------------------------------------------------------------- */ |
| |
| START_TEST(test_methods_isolation) |
| { |
| static const http_request_method_t others[] = { |
| HTTP_REQUEST_POST, HTTP_REQUEST_PUT, HTTP_REQUEST_DELETE, |
| HTTP_REQUEST_OPTIONS, HTTP_REQUEST_HEAD, HTTP_REQUEST_PATCH, |
| HTTP_REQUEST_TRACE, HTTP_REQUEST_INVALID, |
| }; |
| |
| http_router_bind(HTTP_REQUEST_GET, "/jobs", stub_generic, NULL, NULL); |
| |
| ck_assert_int_eq(do_request(HTTP_REQUEST_GET, "/jobs"), RC_GENERIC); |
| |
| for (int i = 0; i < ARRAY_SIZE(others); i++) |
| ck_assert_int_eq(do_request(others[i], "/jobs"), RC_NOT_FOUND); |
| } |
| |
| END_TEST |
| |
| START_TEST(test_slash_normalization) |
| { |
| http_router_bind(HTTP_REQUEST_GET, "/jobs", stub_generic, NULL, NULL); |
| |
| ck_assert_int_eq(do_request(HTTP_REQUEST_GET, "/jobs"), RC_GENERIC); |
| ck_assert_int_eq(do_request(HTTP_REQUEST_GET, "/jobs/"), RC_GENERIC); |
| ck_assert_int_eq(do_request(HTTP_REQUEST_GET, "//jobs"), RC_GENERIC); |
| ck_assert_int_eq(do_request(HTTP_REQUEST_GET, "//jobs//"), RC_GENERIC); |
| ck_assert_int_eq(do_request(HTTP_REQUEST_GET, "/jobs////"), RC_GENERIC); |
| } |
| |
| END_TEST |
| |
| START_TEST(test_percent_decode_valid) |
| { |
| http_router_bind(HTTP_REQUEST_GET, "/jobs", stub_generic, NULL, NULL); |
| /* A literal segment that contains a decoded '/' from %2F. */ |
| http_router_bind(HTTP_REQUEST_GET, "/a%2Fb", stub_generic, NULL, NULL); |
| |
| /* %6A and %6a both decode to 'j'. */ |
| ck_assert_int_eq(do_request(HTTP_REQUEST_GET, "/%6Aobs"), RC_GENERIC); |
| ck_assert_int_eq(do_request(HTTP_REQUEST_GET, "/%6aobs"), RC_GENERIC); |
| |
| /* %2F is decoded into the segment, not treated as a separator. */ |
| ck_assert_int_eq(do_request(HTTP_REQUEST_GET, "/a%2Fb"), RC_GENERIC); |
| ck_assert_int_eq(do_request(HTTP_REQUEST_GET, "/a/b"), RC_NOT_FOUND); |
| } |
| |
| END_TEST |
| |
| START_TEST(test_percent_decode_invalid) |
| { |
| http_router_bind(HTTP_REQUEST_GET, "/jobs", stub_generic, NULL, NULL); |
| |
| ck_assert_int_eq(do_request(HTTP_REQUEST_GET, "/jobs%00x"), |
| RC_NOT_FOUND); |
| ck_assert_int_eq(do_request(HTTP_REQUEST_GET, "/jobs%FFx"), |
| RC_NOT_FOUND); |
| ck_assert_int_eq(do_request(HTTP_REQUEST_GET, "/jobs%ZZx"), |
| RC_NOT_FOUND); |
| ck_assert_int_eq(do_request(HTTP_REQUEST_GET, "/jobs%4"), RC_NOT_FOUND); |
| ck_assert_int_eq(do_request(HTTP_REQUEST_GET, "/jobs%"), RC_NOT_FOUND); |
| } |
| |
| END_TEST |
| |
| START_TEST(test_illegal_chars) |
| { |
| static const char *paths[] = { |
| "/jobs ", "/jo,bs", "/jobs?x", "/jo:bs", |
| "/jo@bs", "/jo!bs", "/jo#bs", |
| }; |
| |
| http_router_bind(HTTP_REQUEST_GET, "/jobs", stub_generic, NULL, NULL); |
| |
| for (int i = 0; i < ARRAY_SIZE(paths); i++) |
| ck_assert_int_eq(do_request(HTTP_REQUEST_GET, paths[i]), |
| RC_NOT_FOUND); |
| } |
| |
| END_TEST |
| |
| START_TEST(test_literal_brace_request) |
| { |
| http_router_bind(HTTP_REQUEST_GET, "/job/{job_id}", stub_job_id, NULL, |
| NULL); |
| |
| /* A literal '{' in a request is invalid -> not found. */ |
| ck_assert_int_eq(do_request(HTTP_REQUEST_GET, "/job/{x}"), |
| RC_NOT_FOUND); |
| /* Positive control: the template still resolves a concrete value. */ |
| ck_assert_int_eq(do_request(HTTP_REQUEST_GET, "/job/123"), RC_JOB_ID); |
| } |
| |
| END_TEST |
| |
| START_TEST(test_dot_segment_not_normalized) |
| { |
| http_router_bind(HTTP_REQUEST_GET, "/a/b", stub_generic, NULL, NULL); |
| |
| /* The router does not collapse "." or ".." dot-segments. */ |
| ck_assert_int_eq(do_request(HTTP_REQUEST_GET, "/a/./b"), RC_NOT_FOUND); |
| ck_assert_int_eq(do_request(HTTP_REQUEST_GET, "/a/b/.."), RC_NOT_FOUND); |
| /* Positive control. */ |
| ck_assert_int_eq(do_request(HTTP_REQUEST_GET, "/a/b"), RC_GENERIC); |
| } |
| |
| END_TEST |
| |
| START_TEST(test_case_sensitivity) |
| { |
| http_router_bind(HTTP_REQUEST_GET, "/Jobs", stub_generic, NULL, NULL); |
| |
| ck_assert_int_eq(do_request(HTTP_REQUEST_GET, "/jobs"), RC_NOT_FOUND); |
| ck_assert_int_eq(do_request(HTTP_REQUEST_GET, "/Jobs"), RC_GENERIC); |
| } |
| |
| END_TEST |
| |
| START_TEST(test_exact_before_template) |
| { |
| /* Bind the template first to prove literals still win. */ |
| http_router_bind(HTTP_REQUEST_POST, SLURM_P "/job/{job_id}", |
| stub_job_id, NULL, NULL); |
| http_router_bind(HTTP_REQUEST_POST, SLURM_P "/job/submit", stub_submit, |
| NULL, NULL); |
| http_router_bind(HTTP_REQUEST_POST, SLURM_P "/job/allocate", |
| stub_generic, NULL, NULL); |
| |
| ck_assert_int_eq(do_request(HTTP_REQUEST_POST, SLURM_P "/job/submit"), |
| RC_SUBMIT); |
| ck_assert_int_eq(do_request(HTTP_REQUEST_POST, SLURM_P "/job/allocate"), |
| RC_GENERIC); |
| ck_assert_int_eq(do_request(HTTP_REQUEST_POST, SLURM_P "/job/999"), |
| RC_JOB_ID); |
| } |
| |
| END_TEST |
| |
| START_TEST(test_handler_and_template_coexist) |
| { |
| http_router_bind(HTTP_REQUEST_GET, DBD_P "/qos/", stub_qos_list, NULL, |
| NULL); |
| http_router_bind(HTTP_REQUEST_GET, DBD_P "/qos/{qos}", stub_qos_one, |
| NULL, NULL); |
| |
| /* Handler on the node itself. */ |
| ck_assert_int_eq(do_request(HTTP_REQUEST_GET, DBD_P "/qos"), |
| RC_QOS_LIST); |
| /* Templated child of the same node. */ |
| ck_assert_int_eq(do_request(HTTP_REQUEST_GET, DBD_P "/qos/normal"), |
| RC_QOS_ONE); |
| } |
| |
| END_TEST |
| |
| START_TEST(test_prefix_and_overlong) |
| { |
| http_router_bind(HTTP_REQUEST_GET, "/a/b/c", stub_generic, NULL, NULL); |
| |
| /* Intermediate nodes have no handler. */ |
| ck_assert_int_eq(do_request(HTTP_REQUEST_GET, "/a"), RC_NOT_FOUND); |
| ck_assert_int_eq(do_request(HTTP_REQUEST_GET, "/a/b"), RC_NOT_FOUND); |
| /* Overlong path past the bound leaf. */ |
| ck_assert_int_eq(do_request(HTTP_REQUEST_GET, "/a/b/c/d"), |
| RC_NOT_FOUND); |
| /* A long request segment must not match and must not crash. */ |
| ck_assert_int_eq(do_request(HTTP_REQUEST_GET, |
| "/a/b/c/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), |
| RC_NOT_FOUND); |
| /* Exact match. */ |
| ck_assert_int_eq(do_request(HTTP_REQUEST_GET, "/a/b/c"), RC_GENERIC); |
| } |
| |
| END_TEST |
| |
| START_TEST(test_root_and_empty) |
| { |
| ck_assert_int_eq(do_request(HTTP_REQUEST_GET, "/"), RC_NOT_FOUND); |
| ck_assert_int_eq(do_request(HTTP_REQUEST_GET, ""), RC_NOT_FOUND); |
| ck_assert_int_eq(do_request(HTTP_REQUEST_GET, NULL), RC_NOT_FOUND); |
| } |
| |
| END_TEST |
| |
| START_TEST(test_path_arg_passthrough) |
| { |
| http_router_bind(HTTP_REQUEST_GET, "/jobs", stub_generic, NULL, |
| (void *) 0xBEEF); |
| |
| ck_assert_int_eq(do_request(HTTP_REQUEST_GET, "/jobs"), RC_GENERIC); |
| ck_assert_ptr_eq(obs.last_path_arg, (void *) 0xBEEF); |
| ck_assert_ptr_eq(obs.last_arg, REQUEST_ARG); |
| } |
| |
| END_TEST |
| |
| START_TEST(test_fini_calls_on_fini) |
| { |
| http_router_bind(HTTP_REQUEST_GET, "/jobs", stub_generic, stub_fini, |
| NULL); |
| http_router_bind(HTTP_REQUEST_GET, "/nodes", stub_generic, stub_fini, |
| NULL); |
| |
| ck_assert_int_eq(fini_calls, 0); |
| |
| http_router_fini(); |
| |
| ck_assert_int_eq(fini_calls, 2); |
| } |
| |
| END_TEST |
| |
| extern int main(int argc, char **argv) |
| { |
| int failures; |
| |
| TCase *tcase = tcase_create("http_router"); |
| tcase_add_unchecked_fixture(tcase, setup, teardown); |
| |
| /* (b) RFC 3986 / HTTP method matrix */ |
| tcase_add_test(tcase, test_methods_isolation); |
| tcase_add_test(tcase, test_slash_normalization); |
| tcase_add_test(tcase, test_percent_decode_valid); |
| tcase_add_test(tcase, test_percent_decode_invalid); |
| tcase_add_test(tcase, test_illegal_chars); |
| tcase_add_test(tcase, test_literal_brace_request); |
| tcase_add_test(tcase, test_dot_segment_not_normalized); |
| tcase_add_test(tcase, test_case_sensitivity); |
| tcase_add_test(tcase, test_exact_before_template); |
| tcase_add_test(tcase, test_handler_and_template_coexist); |
| tcase_add_test(tcase, test_prefix_and_overlong); |
| tcase_add_test(tcase, test_root_and_empty); |
| tcase_add_test(tcase, test_path_arg_passthrough); |
| tcase_add_test(tcase, test_fini_calls_on_fini); |
| |
| /* (a) endpoint sweep over every slurmrestd (method, path) pair */ |
| tcase_add_loop_test(tcase, test_endpoint_sweep, 0, ENDPOINT_COUNT); |
| |
| Suite *suite = suite_create("http_router"); |
| suite_add_tcase(suite, tcase); |
| |
| SRunner *sr = srunner_create(suite); |
| srunner_run_all(sr, CK_VERBOSE); |
| failures = srunner_ntests_failed(sr); |
| srunner_free(sr); |
| |
| return failures; |
| } |