Skip to content

Commit

Permalink
Add a ?case=original query argument to `GET /gov/service/javascript…
Browse files Browse the repository at this point in the history
…-app` (#6712)
  • Loading branch information
eddyashton authored Dec 19, 2024
1 parent 8306717 commit e252947
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 13 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ and this project adheres Fto [Semantic Versioning](http://semver.org/spec/v2.0.0

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

### Added

- `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.

### Deprecated

- 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()`.
Expand Down
60 changes: 47 additions & 13 deletions src/node/gov/handlers/service_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -283,11 +283,35 @@ namespace ccf::gov::endpoints
{
auto endpoints = nlohmann::json::object();

bool original_case = false;
{
const auto parsed_query =
ccf::http::parse_query(ctx.rpc_ctx->get_request_query());
std::string error_reason;
const auto case_opt = ccf::http::get_query_value_opt<std::string>(
parsed_query, "case", error_reason);

if (case_opt.has_value())
{
if (case_opt.value() != "original")
{
ctx.rpc_ctx->set_error(
HTTP_STATUS_BAD_REQUEST,
ccf::errors::InvalidQueryParameterValue,
"Accepted values for the 'case' query parameter are: "
"original");
return;
}

original_case = true;
}
}

auto js_endpoints_handle =
ctx.tx.template ro<ccf::endpoints::EndpointsMap>(
ccf::endpoints::Tables::ENDPOINTS);
js_endpoints_handle->foreach(
[&endpoints](
[&endpoints, original_case](
const ccf::endpoints::EndpointKey& key,
const ccf::endpoints::EndpointProperties& properties) {
auto ib =
Expand All @@ -296,20 +320,29 @@ namespace ccf::gov::endpoints

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

operation["jsModule"] = properties.js_module;
operation["jsFunction"] = properties.js_function;
operation["forwardingRequired"] =
properties.forwarding_required;

auto policies = nlohmann::json::array();
for (const auto& policy : properties.authn_policies)
if (original_case)
{
policies.push_back(policy);
operation = properties;
}
else
{
operation["jsModule"] = properties.js_module;
operation["jsFunction"] = properties.js_function;
operation["forwardingRequired"] =
properties.forwarding_required;
operation["redirectionStrategy"] =
properties.redirection_strategy;

auto policies = nlohmann::json::array();
for (const auto& policy : properties.authn_policies)
{
policies.push_back(policy);
}
operation["authnPolicies"] = policies;

operation["mode"] = properties.mode;
operation["openApi"] = properties.openapi;
}
operation["authnPolicies"] = policies;

operation["mode"] = properties.mode;
operation["openApi"] = properties.openapi;

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

Expand All @@ -330,6 +363,7 @@ namespace ccf::gov::endpoints
HTTP_GET,
api_version_adapter(get_javascript_app, ApiVersion::v1),
no_auth_required)
.add_query_parameter<std::string>("case")
.set_openapi_hidden(true)
.install();

Expand Down
71 changes: 71 additions & 0 deletions tests/js-modules/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,36 @@ def test_module_import(network, args):
return network


def compare_app_metadata(expected, actual, api_key_renames, route=[]):
path = ".".join(route)
assert isinstance(
actual, type(actual)
), f"Expected same type of values at {path}, found {type(expected)} vs {type(actual)}"

if isinstance(expected, dict):
for orig_k, v_expected in expected.items():
k = orig_k
if k in api_key_renames:
k = api_key_renames[k]

assert (
k in actual
), f"Expected key {k} (normalised from {orig_k}) at {path}, found: {actual}"
v_actual = actual[k]

compare_app_metadata(v_expected, v_actual, api_key_renames, route + [k])
else:
if not isinstance(expected, list) and expected in api_key_renames:
k = api_key_renames[expected]
assert (
k == actual
), f"Mismatch at {path}, expected {k} (normalised from {expected}) and found {actual}"
else:
assert (
expected == actual
), f"Mismatch at {path}, expected {expected} and found {actual}"


@reqs.description("Test module access")
def test_module_access(network, args):
primary, _ = network.find_nodes()
Expand All @@ -48,8 +78,49 @@ def test_module_access(network, args):
network.consortium.set_js_app_from_bundle(primary, bundle)

expected_modules = bundle["modules"]
expected_metadata = bundle["metadata"]

http_methods_renamed = {
method: method.upper() for method in ("post", "get", "put", "delete")
}
module_names_prefixed = {
module["name"]: f"/{module['name']}"
for module in expected_modules
if not module["name"].startswith("/")
}
endpoint_def_camelcased = {
"js_module": "jsModule",
"js_function": "jsFunction",
"forwarding_required": "forwardingRequired",
"redirection_strategy": "redirectionStrategy",
"authn_policies": "authnPolicies",
"openapi": "openApi",
}

with primary.api_versioned_client(api_version=args.gov_api_version) as c:
r = c.get("/gov/service/javascript-app?case=original")
assert r.status_code == http.HTTPStatus.OK, r.status_code
compare_app_metadata(
expected_metadata,
r.body.json(),
{
**http_methods_renamed,
**module_names_prefixed,
},
)

r = c.get("/gov/service/javascript-app")
assert r.status_code == http.HTTPStatus.OK, r.status_code
compare_app_metadata(
expected_metadata,
r.body.json(),
{
**http_methods_renamed,
**module_names_prefixed,
**endpoint_def_camelcased,
},
)

r = c.get("/gov/service/javascript-modules")
assert r.status_code == http.HTTPStatus.OK, r.status_code

Expand Down

0 comments on commit e252947

Please sign in to comment.