diff --git a/core/database/CMakeLists.txt b/core/database/CMakeLists.txt index f218bf386..d27b7bcd3 100644 --- a/core/database/CMakeLists.txt +++ b/core/database/CMakeLists.txt @@ -30,6 +30,8 @@ if( ENABLE_FOXX_TESTS ) add_test(NAME foxx_user_router COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_foxx.sh" -t "unit_user_router:") add_test(NAME foxx_task_router COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_foxx.sh" -t "unit_task_router:") add_test(NAME foxx_authz_router COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_foxx.sh" -t "unit_authz_router:") + add_test(NAME foxx_user_router COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_foxx.sh" -t "unit_user_router:") + add_test(NAME foxx_query_router COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_foxx.sh" -t "unit_query_router:") add_test(NAME foxx_unit_user_token COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_foxx.sh" -t "unit_user_token:") add_test(NAME foxx_unit_user_model COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_foxx.sh" -t "unit_user_model:") add_test(NAME foxx_unit_globus_collection_model COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_foxx.sh" -t "unit_globus_collection_model:") @@ -52,6 +54,7 @@ if( ENABLE_FOXX_TESTS ) set_tests_properties(foxx_validation_repo PROPERTIES FIXTURES_REQUIRED Foxx) set_tests_properties(foxx_path PROPERTIES FIXTURES_REQUIRED Foxx) set_tests_properties(foxx_user_router PROPERTIES FIXTURES_REQUIRED "Foxx;FoxxDBFixtures") + set_tests_properties(foxx_query_router PROPERTIES FIXTURES_REQUIRED Foxx) set_tests_properties(foxx_task_router PROPERTIES FIXTURES_REQUIRED Foxx) set_tests_properties(foxx_unit_user_token PROPERTIES FIXTURES_REQUIRED Foxx) set_tests_properties(foxx_unit_user_model PROPERTIES FIXTURES_REQUIRED "Foxx;FoxxDBFixtures") diff --git a/core/database/foxx/api/query_router.js b/core/database/foxx/api/query_router.js index 9e26b2e19..30de93454 100644 --- a/core/database/foxx/api/query_router.js +++ b/core/database/foxx/api/query_router.js @@ -8,6 +8,8 @@ const error = require("./lib/error_codes"); const g_db = require("@arangodb").db; const g_graph = require("@arangodb/general-graph")._graph("sdmsg"); const g_lib = require("./support"); +const logger = require("./lib/logger"); +const basePath = "qry"; module.exports = router; @@ -15,16 +17,24 @@ module.exports = router; router .post("/create", function (req, res) { + let client = undefined; + let result = undefined; try { - var result; - g_db._executeTransaction({ collections: { read: ["u", "uuid", "accn", "admin"], write: ["q", "owner"], }, action: function () { - const client = g_lib.getUserFromClientID(req.queryParams.client); + client = g_lib.getUserFromClientID(req.queryParams.client); + logger.logRequestStarted({ + client: client?._id, + correlationId: req.headers["x-correlation-id"], + httpVerb: "POST", + routePath: basePath + "/create", + status: "Started", + description: "Create Query", + }); // Check max number of saved queries if (client.max_sav_qry >= 0) { @@ -82,7 +92,27 @@ router }); res.send(result); + logger.logRequestSuccess({ + client: client?._id, + correlationId: req.headers["x-correlation-id"], + httpVerb: "POST", + routePath: basePath + "/create", + status: "Success", + description: "Create Query", + extra: result, + }); } catch (e) { + logger.logRequestFailure({ + client: client?._id, + correlationId: req.headers["x-correlation-id"], + httpVerb: "POST", + routePath: basePath + "/create", + status: "Failure", + description: "Create Query", + extra: result, + error: e, + }); + g_lib.handleException(e, res); } }) @@ -106,16 +136,25 @@ router router .post("/update", function (req, res) { + let client = undefined; + let result = undefined; try { - var result; - g_db._executeTransaction({ collections: { read: ["u", "uuid", "accn", "admin"], write: ["q", "owner"], }, action: function () { - const client = g_lib.getUserFromClientID(req.queryParams.client); + client = g_lib.getUserFromClientID(req.queryParams.client); + logger.logRequestStarted({ + client: client?._id, + correlationId: req.headers["x-correlation-id"], + httpVerb: "POST", + routePath: basePath + "/update", + status: "Started", + description: "Update a saved query", + }); + var qry = g_db.q.document(req.body.id); if (client._id != qry.owner && !client.is_admin) { @@ -159,9 +198,27 @@ router result = qry; }, }); - res.send(result); + logger.logRequestSuccess({ + client: client?._id, + correlationId: req.headers["x-correlation-id"], + httpVerb: "POST", + routePath: basePath + "/update", + status: "Success", + description: "Update a saved query", + extra: result, + }); } catch (e) { + logger.logRequestFailure({ + client: client?._id, + correlationId: req.headers["x-correlation-id"], + httpVerb: "POST", + routePath: basePath + "/update", + status: "Failure", + description: "Update a saved query", + extra: result, + error: e, + }); g_lib.handleException(e, res); } }) @@ -186,9 +243,19 @@ router router .get("/view", function (req, res) { + let client = undefined; + let qry = undefined; try { - const client = g_lib.getUserFromClientID(req.queryParams.client); - var qry = g_db.q.document(req.queryParams.id); + client = g_lib.getUserFromClientID(req.queryParams.client); + logger.logRequestStarted({ + client: client?._id, + correlationId: req.headers["x-correlation-id"], + httpVerb: "GET", + routePath: basePath + "/view", + status: "Started", + description: "View specified query", + }); + qry = g_db.q.document(req.queryParams.id); if (client._id != qry.owner && !client.is_admin) { throw error.ERR_PERM_DENIED; @@ -205,7 +272,27 @@ router delete qry.lmit; res.send(qry); + logger.logRequestSuccess({ + client: client?._id, + correlationId: req.headers["x-correlation-id"], + httpVerb: "GET", + routePath: basePath + "/view", + status: "Success", + description: "View specified query", + extra: qry, + }); } catch (e) { + logger.logRequestFailure({ + client: client?._id, + correlationId: req.headers["x-correlation-id"], + httpVerb: "GET", + routePath: basePath + "/view", + status: "Failure", + description: "View specified query", + extra: qry, + error: e, + }); + g_lib.handleException(e, res); } }) @@ -216,9 +303,18 @@ router router .get("/delete", function (req, res) { + let client = undefined; try { - const client = g_lib.getUserFromClientID(req.queryParams.client); + client = g_lib.getUserFromClientID(req.queryParams.client); var owner; + logger.logRequestStarted({ + client: client?._id, + correlationId: req.headers["x-correlation-id"], + httpVerb: "GET", + routePath: basePath + "/delete", + status: "Started", + description: "Delete specified query", + }); for (var i in req.queryParams.ids) { if (!req.queryParams.ids[i].startsWith("q/")) { @@ -243,8 +339,28 @@ router } g_graph.q.remove(owner._from); + logger.logRequestSuccess({ + client: client?._id, + correlationId: req.headers["x-correlation-id"], + httpVerb: "GET", + routePath: basePath + "/delete", + status: "Success", + description: "Delete specified query", + extra: req.queryParams.ids[i], + }); } } catch (e) { + logger.logRequestFailure({ + client: client?._id, + correlationId: req.headers["x-correlation-id"], + httpVerb: "GET", + routePath: basePath + "/delete", + status: "Failure", + description: "Delete specified query", + extra: req.queryParams.ids[i], + error: e, + }); + g_lib.handleException(e, res); } }) @@ -255,12 +371,21 @@ router router .get("/list", function (req, res) { + let client = undefined; + let result = undefined; try { - const client = g_lib.getUserFromClientID(req.queryParams.client); + client = g_lib.getUserFromClientID(req.queryParams.client); + logger.logRequestStarted({ + client: client?._id, + correlationId: req.headers["x-correlation-id"], + httpVerb: "GET", + routePath: basePath + "/list", + status: "Started", + description: "List client saved queries", + }); var qry = "for v in 1..1 inbound @user owner filter is_same_collection('q',v) sort v.title"; - var result; if (req.queryParams.offset != undefined && req.queryParams.count != undefined) { qry += " limit " + req.queryParams.offset + ", " + req.queryParams.count; @@ -292,7 +417,29 @@ router } res.send(result); + logger.logRequestSuccess({ + client: client?._id, + correlationId: req.headers["x-correlation-id"], + httpVerb: "GET", + routePath: basePath + "/list", + status: "Success", + description: "List client saved queries", + extra: { + queryParams: req.queryParams, + _countTotal: result._countTotal, + }, + }); } catch (e) { + logger.logRequestFailure({ + client: client?._id, + correlationId: req.headers["x-correlation-id"], + httpVerb: "GET", + routePath: basePath + "/list", + status: "Failure", + description: "List client saved queries", + extra: result, + error: e, + }); g_lib.handleException(e, res); } }) @@ -504,8 +651,19 @@ function execQuery(client, mode, published, orig_query) { router .get("/exec", function (req, res) { + let client = undefined; + let results = undefined; try { - const client = g_lib.getUserFromClientID(req.queryParams.client); + client = g_lib.getUserFromClientID(req.queryParams.client); + logger.logRequestStarted({ + client: client?._id, + correlationId: req.headers["x-correlation-id"], + httpVerb: "GET", + routePath: basePath + "/exec", + status: "Started", + description: "Execute specified queries", + }); + var qry = g_db.q.document(req.queryParams.id); if (client._id != qry.owner && !client.is_admin) { @@ -517,10 +675,29 @@ router qry.params.cnt = req.queryParams.count; } - var results = execQuery(client, qry.query.mode, qry.query.published, qry); + results = execQuery(client, qry.query.mode, qry.query.published, qry); res.send(results); + logger.logRequestSuccess({ + client: client?._id, + correlationId: req.headers["x-correlation-id"], + httpVerb: "GET", + routePath: basePath + "/exec", + status: "Success", + description: "Execute specified queries", + extra: results, + }); } catch (e) { + logger.logRequestFailure({ + client: client?._id, + correlationId: req.headers["x-correlation-id"], + httpVerb: "GET", + routePath: basePath + "/exec", + status: "Failure", + description: "Execute specified queries", + extra: results, + error: e, + }); g_lib.handleException(e, res); } }) @@ -533,17 +710,46 @@ router router .post("/exec/direct", function (req, res) { + let results = undefined; + let client = undefined; try { - const client = g_lib.getUserFromClientID_noexcept(req.queryParams.client); + client = g_lib.getUserFromClientID_noexcept(req.queryParams.client); + logger.logRequestStarted({ + client: client?._id, + correlationId: req.headers["x-correlation-id"], + httpVerb: "POST", + routePath: basePath + "/exec/direct", + status: "Started", + description: "Execute published data search query", + }); const query = { ...req.body, params: JSON.parse(req.body.params), }; - var results = execQuery(client, req.body.mode, req.body.published, query); + results = execQuery(client, req.body.mode, req.body.published, query); res.send(results); + logger.logRequestSuccess({ + client: client?._id, + correlationId: req.headers["x-correlation-id"], + httpVerb: "POST", + routePath: basePath + "/exec/direct", + status: "Success", + description: "Execute published data search query", + extra: results, + }); } catch (e) { + logger.logRequestFailure({ + client: client?._id, + correlationId: req.headers["x-correlation-id"], + httpVerb: "POST", + routePath: basePath + "/exec/direct", + status: "Failure", + description: "Execute published data search query", + extra: results, + error: e, + }); g_lib.handleException(e, res); } }) diff --git a/core/database/foxx/tests/query_router.test.js b/core/database/foxx/tests/query_router.test.js new file mode 100644 index 000000000..b075e6790 --- /dev/null +++ b/core/database/foxx/tests/query_router.test.js @@ -0,0 +1,166 @@ +"use strict"; +// NOTE: completion of tests requires successful run of user_fixture.js script + +// Need to pull enum from support +const g_lib = require("../api/support"); + +// Integration test of API +const { expect } = require("chai"); +const request = require("@arangodb/request"); +const { baseUrl } = module.context; +const { db } = require("@arangodb"); + +const qry_base_url = `${baseUrl}/qry`; + +describe("unit_query_router: the Foxx microservice qry_router endpoints", () => { + after(function () { + const collections = ["u", "qry"]; + collections.forEach((name) => { + let col = db._collection(name); + if (col) col.truncate(); + }); + }); + + beforeEach(() => { + const collections = ["u", "qry"]; + collections.forEach((name) => { + let col = db._collection(name); + if (col) { + col.truncate(); // truncate after ensuring collection exists + } else { + db._create(name); // create if it doesn’t exist + } + }); + }); + + it("should successfully run the create route", () => { + db.u.save({ + _key: "fakeUser", + _id: "u/fakeUser", + name: "fake user", + name_first: "fake", + name_last: "user", + is_admin: true, + max_coll: 50, + max_proj: 10, + max_sav_qry: 20, + email: "fakeuser@gmail.com", + }); + + // Arrange + const request_string = `${qry_base_url}/create?client=u/fakeUser`; + + const body = { + title: "My Query", + qry_begin: "FOR i IN something", + qry_end: "RETURN i", + qry_filter: "", + params: {}, + limit: 10, + query: {}, // adjust if necessary + }; + + const response = request.post(request_string, { + json: true, + body: body, + headers: { + "x-correlation-id": "test-correlation-id", + }, + }); + + // Assert + expect(response.status).to.equal(200); + }); + + it("should fail running the create route", () => { + db.u.save({ + _key: "fakeUser", + _id: "u/fakeUser", + name: "fake user", + name_first: "fake", + name_last: "user", + is_admin: true, + max_coll: 50, + max_proj: 10, + max_sav_qry: 20, + email: "fakeuser@gmail.com", + }); + + // Arrange + const request_string = `${qry_base_url}/create?client=u/wellthiswasunexpected`; + + const body = { + title: "My Query", + qry_begin: "FOR i IN something", + qry_end: "RETURN i", + qry_filter: "", + params: {}, + limit: 10, + query: {}, // adjust if necessary + }; + + const response = request.post(request_string, { + json: true, + body: body, + headers: { + "x-correlation-id": "test-correlation-id", + }, + }); + + // Assert + expect(response.status).to.equal(400); + }); + + it("should return a list of saved queries for a valid user", () => { + // arrange + const fakeUser = { + _key: "fakeUser", + _id: "u/fakeUser", + name: "Fake User", + email: "fakeuser@datadev.org", + is_admin: false, + max_coll: 5, + max_proj: 5, + max_sav_qry: 10, + }; + + db.u.save(fakeUser); + + // Save the query and the edge between the query and the user + var request_string = `${qry_base_url}/create?client=u/fakeUser`; + + var body = { + title: "Test Query Title", + qry_begin: "FOR i IN something", + qry_end: "RETURN i", + qry_filter: "", + params: {}, + limit: 10, + query: {}, // adjust if necessary + }; + + var response = request.post(request_string, { + json: true, + body: body, + headers: { + "x-correlation-id": "test-correlation-id", + }, + }); + + request_string = `${qry_base_url}/list?client=u/fakeUser`; + + // act + response = request.get(request_string, { + headers: { + "x-correlation-id": "test-correlation-id", + }, + }); + + var parsed = JSON.parse(response.body); + console.log("Response body:", response.body); + // assert + expect(response.status).to.equal(200); + expect(parsed).to.be.an("array"); + expect(parsed.length).to.be.greaterThan(0); + }); +});