Skip to content

Commit e252947

Browse files
authored
Add a ?case=original query argument to GET /gov/service/javascript-app (#6712)
1 parent 8306717 commit e252947

File tree

3 files changed

+122
-13
lines changed

3 files changed

+122
-13
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ and this project adheres Fto [Semantic Versioning](http://semver.org/spec/v2.0.0
99

1010
[6.0.0-dev11]: https://github.com/microsoft/CCF/releases/tag/6.0.0-dev11
1111

12+
### Added
13+
14+
- `GET /gov/service/javascript-app` now takes an optional `?case=original` query argument. When passed, the response will contain the raw original `snake_case` field names, for direct comparison, rather than the API-standard `camelCase` projections.
15+
1216
### Deprecated
1317

1418
- The function `ccf::get_js_plugins()` and associated FFI plugin system for JS is deprecated. Similar functionality should now be implemented through a `js::Extension` returned from `DynamicJSEndpointRegistry::get_extensions()`.

src/node/gov/handlers/service_state.h

+47-13
Original file line numberDiff line numberDiff line change
@@ -283,11 +283,35 @@ namespace ccf::gov::endpoints
283283
{
284284
auto endpoints = nlohmann::json::object();
285285

286+
bool original_case = false;
287+
{
288+
const auto parsed_query =
289+
ccf::http::parse_query(ctx.rpc_ctx->get_request_query());
290+
std::string error_reason;
291+
const auto case_opt = ccf::http::get_query_value_opt<std::string>(
292+
parsed_query, "case", error_reason);
293+
294+
if (case_opt.has_value())
295+
{
296+
if (case_opt.value() != "original")
297+
{
298+
ctx.rpc_ctx->set_error(
299+
HTTP_STATUS_BAD_REQUEST,
300+
ccf::errors::InvalidQueryParameterValue,
301+
"Accepted values for the 'case' query parameter are: "
302+
"original");
303+
return;
304+
}
305+
306+
original_case = true;
307+
}
308+
}
309+
286310
auto js_endpoints_handle =
287311
ctx.tx.template ro<ccf::endpoints::EndpointsMap>(
288312
ccf::endpoints::Tables::ENDPOINTS);
289313
js_endpoints_handle->foreach(
290-
[&endpoints](
314+
[&endpoints, original_case](
291315
const ccf::endpoints::EndpointKey& key,
292316
const ccf::endpoints::EndpointProperties& properties) {
293317
auto ib =
@@ -296,20 +320,29 @@ namespace ccf::gov::endpoints
296320

297321
auto operation = nlohmann::json::object();
298322

299-
operation["jsModule"] = properties.js_module;
300-
operation["jsFunction"] = properties.js_function;
301-
operation["forwardingRequired"] =
302-
properties.forwarding_required;
303-
304-
auto policies = nlohmann::json::array();
305-
for (const auto& policy : properties.authn_policies)
323+
if (original_case)
306324
{
307-
policies.push_back(policy);
325+
operation = properties;
326+
}
327+
else
328+
{
329+
operation["jsModule"] = properties.js_module;
330+
operation["jsFunction"] = properties.js_function;
331+
operation["forwardingRequired"] =
332+
properties.forwarding_required;
333+
operation["redirectionStrategy"] =
334+
properties.redirection_strategy;
335+
336+
auto policies = nlohmann::json::array();
337+
for (const auto& policy : properties.authn_policies)
338+
{
339+
policies.push_back(policy);
340+
}
341+
operation["authnPolicies"] = policies;
342+
343+
operation["mode"] = properties.mode;
344+
operation["openApi"] = properties.openapi;
308345
}
309-
operation["authnPolicies"] = policies;
310-
311-
operation["mode"] = properties.mode;
312-
operation["openApi"] = properties.openapi;
313346

314347
operations[key.verb.c_str()] = operation;
315348

@@ -330,6 +363,7 @@ namespace ccf::gov::endpoints
330363
HTTP_GET,
331364
api_version_adapter(get_javascript_app, ApiVersion::v1),
332365
no_auth_required)
366+
.add_query_parameter<std::string>("case")
333367
.set_openapi_hidden(true)
334368
.install();
335369

tests/js-modules/modules.py

+71
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,36 @@ def test_module_import(network, args):
3939
return network
4040

4141

42+
def compare_app_metadata(expected, actual, api_key_renames, route=[]):
43+
path = ".".join(route)
44+
assert isinstance(
45+
actual, type(actual)
46+
), f"Expected same type of values at {path}, found {type(expected)} vs {type(actual)}"
47+
48+
if isinstance(expected, dict):
49+
for orig_k, v_expected in expected.items():
50+
k = orig_k
51+
if k in api_key_renames:
52+
k = api_key_renames[k]
53+
54+
assert (
55+
k in actual
56+
), f"Expected key {k} (normalised from {orig_k}) at {path}, found: {actual}"
57+
v_actual = actual[k]
58+
59+
compare_app_metadata(v_expected, v_actual, api_key_renames, route + [k])
60+
else:
61+
if not isinstance(expected, list) and expected in api_key_renames:
62+
k = api_key_renames[expected]
63+
assert (
64+
k == actual
65+
), f"Mismatch at {path}, expected {k} (normalised from {expected}) and found {actual}"
66+
else:
67+
assert (
68+
expected == actual
69+
), f"Mismatch at {path}, expected {expected} and found {actual}"
70+
71+
4272
@reqs.description("Test module access")
4373
def test_module_access(network, args):
4474
primary, _ = network.find_nodes()
@@ -48,8 +78,49 @@ def test_module_access(network, args):
4878
network.consortium.set_js_app_from_bundle(primary, bundle)
4979

5080
expected_modules = bundle["modules"]
81+
expected_metadata = bundle["metadata"]
82+
83+
http_methods_renamed = {
84+
method: method.upper() for method in ("post", "get", "put", "delete")
85+
}
86+
module_names_prefixed = {
87+
module["name"]: f"/{module['name']}"
88+
for module in expected_modules
89+
if not module["name"].startswith("/")
90+
}
91+
endpoint_def_camelcased = {
92+
"js_module": "jsModule",
93+
"js_function": "jsFunction",
94+
"forwarding_required": "forwardingRequired",
95+
"redirection_strategy": "redirectionStrategy",
96+
"authn_policies": "authnPolicies",
97+
"openapi": "openApi",
98+
}
5199

52100
with primary.api_versioned_client(api_version=args.gov_api_version) as c:
101+
r = c.get("/gov/service/javascript-app?case=original")
102+
assert r.status_code == http.HTTPStatus.OK, r.status_code
103+
compare_app_metadata(
104+
expected_metadata,
105+
r.body.json(),
106+
{
107+
**http_methods_renamed,
108+
**module_names_prefixed,
109+
},
110+
)
111+
112+
r = c.get("/gov/service/javascript-app")
113+
assert r.status_code == http.HTTPStatus.OK, r.status_code
114+
compare_app_metadata(
115+
expected_metadata,
116+
r.body.json(),
117+
{
118+
**http_methods_renamed,
119+
**module_names_prefixed,
120+
**endpoint_def_camelcased,
121+
},
122+
)
123+
53124
r = c.get("/gov/service/javascript-modules")
54125
assert r.status_code == http.HTTPStatus.OK, r.status_code
55126

0 commit comments

Comments
 (0)