From d6a59665c3f4794cdd0c5c3417e82e638ef8db3d Mon Sep 17 00:00:00 2001 From: amrc-za Date: Fri, 12 Jun 2026 14:30:15 +0100 Subject: [PATCH 1/2] data access created separate structure handler classes --- acs-data-access/lib/api-v1.js | 305 ++++-------------- acs-data-access/lib/base-structure-handler.js | 14 + acs-data-access/lib/session-limits-handler.js | 84 +++++ .../lib/sparkplug-sources-handler.js | 58 ++++ .../lib/unions-components-handler.js | 85 +++++ acs-data-access/lib/utils.js | 10 +- 6 files changed, 316 insertions(+), 240 deletions(-) create mode 100644 acs-data-access/lib/base-structure-handler.js create mode 100644 acs-data-access/lib/session-limits-handler.js create mode 100644 acs-data-access/lib/sparkplug-sources-handler.js create mode 100644 acs-data-access/lib/unions-components-handler.js diff --git a/acs-data-access/lib/api-v1.js b/acs-data-access/lib/api-v1.js index f577ee3b1..22e9ba96f 100644 --- a/acs-data-access/lib/api-v1.js +++ b/acs-data-access/lib/api-v1.js @@ -7,17 +7,14 @@ import express from "express"; import { Map as IMap, Seq as ISeq, merge } from "immutable"; import * as rx from "rxjs"; -import { APIError } from "@amrc-factoryplus/service-api"; import { ServiceError } from "@amrc-factoryplus/service-client"; -import { retryBackoff } from "@amrc-factoryplus/rx-util"; import { DataAccess as Constants } from "./constants.js"; import { valid_uuid, valid_datetime } from "./validate.js"; -import { csv_escape, maxDate, minDate } from './utils.js'; +import { fail, csv_escape, maxDate, minDate } from './utils.js'; -function fail(log, status, message) { - log(message); - throw new APIError(status); - } +import { SparkplugSourcesHandler } from "./sparkplug-sources-handler.js"; +import { SessionLimitsHandler } from "./session-limits-handler.js"; +import { UnionComponentsHandler } from "./unions-components-handler.js"; export class APIv1 { constructor(opts) { @@ -27,6 +24,16 @@ export class APIv1 { this.log = opts.debug.bound("apiv1"); this.influxReader = opts.influxReader; this.routes = this.setup_routes(); + this.handlers = { + [Constants.App.SparkplugSrc]: + new SparkplugSourcesHandler(this), + + [Constants.App.SessionLimits]: + new SessionLimitsHandler(this), + + [Constants.App.UnionComponents]: + new UnionComponentsHandler(this), + }; } setup_routes() { @@ -55,6 +62,17 @@ export class APIv1 { return api; } + + _getHandler(structure) { + const handler = this.handlers[structure]; + + if (!handler) + fail(this.log, 422, `Unknown structure ${structure}`); + + return handler; + } + + async delete_dataset(req, res){ const dataset_uuid = req.params.uuid; if(!dataset_uuid) return fail(this.log, 422, `No req.params.uuid`); @@ -71,9 +89,8 @@ export class APIv1 { this.log(`Delete dataset called by ${req.auth} for ${dataset_uuid}`); - // remove all subclass relationships - - const subclasses = await this.cdb.class_subclasses(dataset_uuid); + // remove all subclass relationships before deleting + const subclasses = await this.cdb.class_direct_subclasses(dataset_uuid); if(subclasses){ for(let s of subclasses){ await this.cdb.class_remove_subclass(dataset_uuid, s); @@ -158,73 +175,10 @@ export class APIv1 { } - // Check if principal has necessary permission to referenced source - async _check_second_level_permission(principal, structure, config){ - if( structure == Constants.App.SparkplugSrc){ - const target = config.source; - if(!target) return fail(this.log, 422, `Dataset definition does not contain source.`); - if(!valid_uuid(target)) return fail(this.log, 422, `Source uuid ${target} is invalid.`); - - const ok = await this.auth.check_acl( - principal, - Constants.Perm.UseSparkplug, - target, - true - ); - - return ok; - } - - if(structure == Constants.App.SessionLimits){ - - const target = config.source; - if(!target) return fail(this.log, 422, `Dataset definition does not contain source.`); - - if(!valid_uuid(target)) return fail(this.log, 422, `Source uuid ${target} is invalid.`); - - const ok = await this.auth.check_acl( - principal, - Constants.Perm.UseForSession, - target, - true - ); - - return ok; - - }else if(structure == Constants.App.UnionComponents){ - if(config.length == 0) return true; - - if(!Array.isArray(config)) return fail(this.log, 422, `Dataset def of structure ${structure} must be an Array.`); - - for(let target of config){ - - const ok = await this.auth.check_acl( - principal, - Constants.Perm.IncludeInUnion, - target, - true - ); - - if(!ok) return false; - } - - return true; - - }else{ - // Unhandled Structure type - return fail(this.log, 422, `Structure ${structure} is unknown`); - } - } - - - async _resolve_dataset( - dataset_uuid, - inherited_range = {from: null, to: null}, - visited = new Set(), - ){ + async resolve_dataset( dataset_uuid, inherited_range = {from: null, to: null}, visited = new Set()){ // prevent circular references if(visited.has(dataset_uuid)) return fail(this.log, 404, `Circular dataset reference detected ${dataset_uuid}`); @@ -236,73 +190,25 @@ export class APIv1 { if(!dataset) return fail(this.log, 404, `Dataset not found ${dataset_uuid}`); const {structure, config} = dataset; + if(!structure) return fail(this.log, 404, `Structure not found for dataset ${dataset_uuid}`); - + if(!valid_uuid(structure)) return (this.log, 404, `Structure uuid is invalid ${structure}`); if(structure === Constants.Special.InvalidDataset) return fail(this.log, 404, `Invalid dataset ${dataset_uuid}`); - if(!config) return fail(this.log, 404, `Config not found for ${dataset_uuid}`); - - - // ====================================================== - // SparkplugSRC - // ====================================================== - - if(structure == Constants.App.SparkplugSrc){ - return [ - { - dataset_uuid, - device_uuid: config.source, - from: inherited_range.from, - to: inherited_range.to, - }, - ]; - } - - // ====================================================== - // SessionLimits - // ====================================================== - if(structure == Constants.App.SessionLimits){ - const session_range = { - from: config.from ?? null, - to: config.to ?? null, - }; - - const merged_range = this._intersectRanges( - inherited_range, - session_range, - ); - - return this._resolve_dataset( - config.source, - merged_range, - visited, - ); - } - - // ====================================================== - // UnionComponents - // ====================================================== - if(structure == Constants.App.UnionComponents){ - if(!Array.isArray(config)) return fail(this.log, 404, `Union dataset config must be an array.`); + const handler = this._getHandler(structure); + handler.validate_config(config); - const results = []; - - for (const src_ds_uuid of config){ - const src_results = await this._resolve_dataset( - src_ds_uuid, - inherited_range, - new Set(visited), - ); - results.push(...src_results); - } - return results; - } - - return fail(this.log, 404, `Unknown dataset structure ${structure}`); + return await handler.resolve({ + dataset_uuid, + dataset, + config, + inherited_range, + visited, + }); } - _intersectRanges(parent, child){ + intersectRanges(parent, child){ const from = maxDate(parent.from, child.from); const to = minDate(parent.to, child.to); @@ -327,12 +233,9 @@ export class APIv1 { * Don't return valid response if dataset is invalid */ async dataset_data(req, res) { - const dataset_uuid = req.params.uuid; - if (!valid_uuid(dataset_uuid)) { - return fail(this.log, 422, `Invalid dataset uuid`); - } + if (!valid_uuid(dataset_uuid)) return fail(this.log, 422, `Invalid dataset uuid`); const ok = await this.auth.check_acl( req.auth, @@ -341,9 +244,7 @@ export class APIv1 { true, ); - if (!ok) { - return fail(this.log, 403, `Unauthorised to read ${dataset_uuid}`); - } + if (!ok) return fail(this.log, 403, `Unauthorised to read ${dataset_uuid}`); const meta = req.body?.measurement ? { measurement: req.body?.measurement } @@ -351,7 +252,7 @@ export class APIv1 { try { // Resolve dataset tree - const resolved_sources = await this._resolve_dataset(dataset_uuid); + const resolved_sources = await this.resolve_dataset(dataset_uuid); res.setHeader("Content-Type", "text/csv"); res.setHeader( @@ -416,7 +317,7 @@ export class APIv1 { true, ); - if (!ok) return fail(this.log, 403, `You don't have permission to Edit dataset ${uuid}.`); + if (!ok) return fail(this.log, 403, `You don't have permission to Edit dataset ${dataset_uuid}.`); const dataset = await rx.firstValueFrom( this.data.allowed_all_datasets(req.auth, Constants.Perm.EditDataset).pipe( @@ -430,58 +331,26 @@ export class APIv1 { } - async _update_dataset_config(principal, structure, config, objectUuid){ - const ok2 = await this._check_second_level_permission(principal, structure, config); + async _update_dataset_config(principal, structure, config, dataset_uuid){ + const handler = this._getHandler(structure); + const ok2 = await handler.check_sources_permissions(principal, config); if(!ok2) return fail(this.log, 403, `You don't have permission for source(s) in config.`); - // Create new Dataset Object - if(!objectUuid){ - objectUuid = await this.cdb.create_object(Constants.Class.Dataset); - this.log("Created new dataset object in ConfigDB", objectUuid); + // Create new Dataset object + if(!dataset_uuid){ + dataset_uuid = await this.cdb.create_object(Constants.Class.Dataset); + this.log("Created new dataset object in ConfigDB", dataset_uuid); } // Create config entry for the dataset object - await this.cdb.put_config(structure, objectUuid, config); - this.log(`Added config for ${objectUuid}`); - - this._create_subclass_relationship(structure, objectUuid, config); + await this.cdb.put_config(structure, dataset_uuid, config); + this.log(`Added config for ${dataset_uuid}`); - this.log(`Created subclass relationship for ${objectUuid}`); - return objectUuid; - } - - async _create_subclass_relationship(structure, dataset_uuid, config) { - const subclasses = - structure == Constants.App.SessionLimits ? [[config.source, dataset_uuid]] - : structure == Constants.App.UnionComponents ? config.map(source => [dataset_uuid, source]) - : []; - for (const subclass of subclasses) { - await rx.lastValueFrom( - rx.defer(() => - this.cdb.class_add_subclass(...subclass) - ).pipe(retryBackoff(500, e => this.log(e))) - ); - } - } - - async _unlink_subclasses(dataset_uuid, structure, config){ - if (structure === Constants.App.SparkplugSrc) return; - else if(structure === Constants.App.SessionLimits){ - // remove the dataset from subclasses of its source dataset - await this.cdb.class_remove_subclass(config.source, dataset_uuid); - this.log(`Removed ${dataset_uuid} from ${config.source} subclasses.`) - - }else if(structure === Constants.App.UnionComponents){ - if(config.length <= 0) return; - - for (const source_uuid of config){ - await this.cdb.class_remove_subclass(dataset_uuid, source_uuid); - this.log(`Removed ${dataset_uuid} from ${source_uuid} subclasses.`); - } - }else{ - return fail(this.log, 404, `Unknown structure type ${structure}`); - } + await handler.create_subclass_relationships(dataset_uuid, config); + this.log(`Created subclass relationship for ${dataset_uuid}`); + + return dataset_uuid; } @@ -492,45 +361,6 @@ export class APIv1 { * @returns new dataset's UUID - JSON string */ - _validate_config(config, structure){ - if(structure === Constants.App.SparkplugSrc){ - const source = config.source; - if(!source) - return fail(this.log, 422, `req.body.config for SparkplugSrc structure type ${structure} must contain "source" field.`); - - if(!valid_uuid(source)) - return fail(this.log, 422, `req.body.config.source uuid is invalid.`); - - } - else if(structure === Constants.App.SessionLimits){ - const {to, from, source} = config; - - if(!to || !from || !source) - return fail(this.log, 422, `req.body.config for SessionLimits type must contain "source", "to" and "from" fields.`); - - if(!valid_uuid(source)) - return fail(this.log, 422, `req.body.config.source uuid is invalid.`); - - if(!valid_datetime(to)) - return fail(this.log, 422, `req.body.config.to datetime format is invalid.`); - - if(!valid_datetime(from)) - return fail(this.log, 422, `req.body.config.from datetime format is invalid.`); - } - else if(structure === Constants.App.UnionComponents){ - if(!Array.isArray(config)) - return fail(this.log, 422, `req.body.config must be array []`); - - for(let src of config){ - if (!valid_uuid(src)) - return fail(this.log, 422, `${src} is invalid UUID`); - } - } - else{ - return fail(this.log, 404, `Unknown structure type ${structure}`); - } - } - async structure_create(req, res){ const {structure, config} = req.body; @@ -538,7 +368,7 @@ export class APIv1 { if(!valid_uuid(structure)) return fail(this.log, 422, `Structure uuid ${structure} is invalid.`); if(!config) return fail(this.log, 422, `Config not provided in request body.`); - this._validate_config(config, structure); + this._getHandler(structure).validate_config(config); const ok = await this.auth.check_acl( req.auth, @@ -549,14 +379,14 @@ export class APIv1 { if (!ok) return fail(this.log, 403, `You don't have Create permission for structure ${structure}`); - const objectUuid = await this._update_dataset_config( + const dataset_uuid = await this._update_dataset_config( req.auth, structure, config, null ); - return res.status(200).json(objectUuid); + return res.status(200).json(dataset_uuid); } @@ -588,7 +418,9 @@ export class APIv1 { const new_config = req.body.config; if(!new_config) return fail(this.log, 422, `Config not provided`); - this._validate_config(new_config, structure); + + const handler = this._getHandler(structure); + handler.validate_config(new_config); const ok = await this.auth.check_acl( req.auth, @@ -596,7 +428,6 @@ export class APIv1 { dataset_uuid, true ); - if (!ok) return fail(this.log, 403, `You don't have Edit permission for dataset ${dataset_uuid}`); const datasets = await rx.firstValueFrom(this.data.allowed_all_datasets(req.auth, Constants.Perm.EditDataset)); @@ -607,18 +438,14 @@ export class APIv1 { const current_config = dataset.config; const is_valid = current_structure !== Constants.Special.InvalidDataset; - // for VALID dataset + // for currently VALID dataset if(is_valid){ if(current_structure != structure) return fail(this.log, 409, `Changing structure type is not allowed (current: ${current_structure}, new: ${structure})`); // remove all subclass relationships with current config sources - await this._unlink_subclasses( - dataset_uuid, - current_structure, - current_config - ); + await handler.remove_subclass_relationships(dataset_uuid, current_config) } - // For INVALID dataset + // For currently INVALID dataset else{ // delete configs for all other structures const all_structure_apps = Object.values(Constants.App); diff --git a/acs-data-access/lib/base-structure-handler.js b/acs-data-access/lib/base-structure-handler.js new file mode 100644 index 000000000..84f6b4224 --- /dev/null +++ b/acs-data-access/lib/base-structure-handler.js @@ -0,0 +1,14 @@ +export class BaseStructureHandler { + constructor(api) { + this.api = api; + this.auth = api.auth; + this.log = api.log; + this.cdb = api.cdb; + } + + validate_config(config) {} + check_source_permission(principal, config) {} + resolve(ctx) {} + create_subclass_relationships(datasetUuid, config) {} + remove_subclass_relationships(datasetUuid, config) {} +} \ No newline at end of file diff --git a/acs-data-access/lib/session-limits-handler.js b/acs-data-access/lib/session-limits-handler.js new file mode 100644 index 000000000..382cf685f --- /dev/null +++ b/acs-data-access/lib/session-limits-handler.js @@ -0,0 +1,84 @@ +import * as rx from "rxjs"; + +import { retryBackoff } from "@amrc-factoryplus/rx-util"; + +import {BaseStructureHandler} from './base-structure-handler.js'; +import { fail } from './utils.js'; +import { valid_uuid, valid_datetime } from "./validate.js"; +import { DataAccess as Constants } from "./constants.js"; + + + +export class SessionLimitsHandler extends BaseStructureHandler { + constructor(api){ + super(api); + + this.structure = Constants.App.SessionLimits; + this.sourcePermission = Constants.Perm.UseForSession; + } + + validate_config(config) { + if(!config) + return fail(this.log, 422, `config not provided`); + + const {to, from, source} = config; + if(!to || !from || !source) + return fail(this.log, 422, `req.body.config for SessionLimits type must contain "source", "to" and "from" fields.`); + + if(!valid_uuid(source)) + return fail(this.log, 422, `req.body.config.source uuid is invalid.`); + + if(!valid_datetime(to)) + return fail(this.log, 422, `req.body.config.to datetime format is invalid.`); + + if(!valid_datetime(from)) + return fail(this.log, 422, `req.body.config.from datetime format is invalid.`); + } + + async check_sources_permissions(principal, config) { + const target = config.source; + if(!target) return fail(this.log, 422, `Dataset definition does not contain source.`); + + if(!valid_uuid(target)) return fail(this.log, 422, `Source uuid ${target} is invalid.`); + + const ok = await this.auth.check_acl( + principal, + this.sourcePermission, + target, + true + ); + + return ok; + } + + async resolve({ config, inherited_range, visited }) { + const session_range = { + from: config.from ?? null, + to: config.to ?? null, + }; + + const merged_range = this.api.intersectRanges( + inherited_range, + session_range, + ); + + return this.api.resolve_dataset( + config.source, + merged_range, + visited, + ); + } + + async create_subclass_relationships(dataset_uuid, config) { + await rx.lastValueFrom( + rx.defer(() => + this.cdb.class_add_subclass(config.source, dataset_uuid) + ).pipe(retryBackoff(500, e => this.log(e))) + ); + } + + async remove_subclass_relationships(dataset_uuid, config) { + await this.cdb.class_remove_subclass(config.source, dataset_uuid); + this.log(`Removed ${dataset_uuid} from ${config.source} subclasses.`) + } +} \ No newline at end of file diff --git a/acs-data-access/lib/sparkplug-sources-handler.js b/acs-data-access/lib/sparkplug-sources-handler.js new file mode 100644 index 000000000..cdbed1308 --- /dev/null +++ b/acs-data-access/lib/sparkplug-sources-handler.js @@ -0,0 +1,58 @@ +import {BaseStructureHandler} from './base-structure-handler.js'; +import { fail } from './utils.js'; +import { valid_uuid } from "./validate.js"; +import { DataAccess as Constants } from "./constants.js"; + +export class SparkplugSourcesHandler extends BaseStructureHandler { + constructor(api){ + super(api); + + this.structure = Constants.App.SparkplugSrc; + this.sourcePermission = Constants.Perm.UseSparkplug; + } + + validate_config(config) { + if(!config) + return fail(this.log, 422, `config not provided`); + + const source = config.source; + if(!source) + return fail(this.log, 422, `config for SparkplugSrc structure type ${this.structure} must contain "source" field.`); + + if(!valid_uuid(source)) + return fail(this.log, 422, `config.source uuid is invalid.`); + } + + async check_sources_permissions(principal, config) { + const target = config.source; + if(!target) return fail(this.log, 422, `Dataset definition does not contain source.`); + + if(!valid_uuid(target)) return fail(this.log, 422, `Source uuid ${target} is invalid.`); + + const ok = await this.auth.check_acl( + principal, + this.sourcePermission, + target, + true + ); + + return ok; + } + + async resolve({ dataset_uuid, config, inherited_range }) { + return [{ + dataset_uuid, + device_uuid: config.source, + from: inherited_range.from, + to: inherited_range.to, + }]; + } + + async create_subclass_relationships() { + return; + } + + async remove_subclass_relationships() { + return; + } +} \ No newline at end of file diff --git a/acs-data-access/lib/unions-components-handler.js b/acs-data-access/lib/unions-components-handler.js new file mode 100644 index 000000000..c5328f100 --- /dev/null +++ b/acs-data-access/lib/unions-components-handler.js @@ -0,0 +1,85 @@ +import * as rx from "rxjs"; + +import { retryBackoff } from "@amrc-factoryplus/rx-util"; + +import {BaseStructureHandler} from './base-structure-handler.js'; +import { fail } from './utils.js'; +import { valid_uuid } from "./validate.js"; +import { DataAccess as Constants } from "./constants.js"; + +export class UnionComponentsHandler extends BaseStructureHandler { + + constructor(api){ + super(api); + + this.structure = Constants.App.UnionComponents; + this.sourcePermission = Constants.Perm.IncludeInUnion; + } + + validate_config(config) { + if(!config) + return fail(this.log, 422, `config not provided`); + + if(!Array.isArray(config)) + return fail(this.log, 422, `config must be array [] for UnionComponents structure`); + + for(let src of config){ + if (!valid_uuid(src)) + return fail(this.log, 422, `${src} is invalid UUID`); + } + } + + async check_sources_permissions(principal, config) { + if(!Array.isArray(config)) return fail(this.log, 422, `Dataset def of structure ${this.structure} must be an Array.`); + + if(config.length == 0) return true; + + for(let target of config){ + const ok = await this.auth.check_acl( + principal, + this.sourcePermission, + target, + true + ); + + if(!ok) return false; + } + + return true; + } + + async resolve({ config, inherited_range, visited }) { + const results = []; + + for (const src of config) { + const src_results = await this.api.resolve_dataset( + src, + inherited_range, + new Set(visited), + ); + + results.push(...src_results); + } + + return results; + } + + async create_subclass_relationships(datasetUuid, config) { + for (const source of config) { + await rx.lastValueFrom( + rx.defer(() => + this.cdb.class_add_subclass(datasetUuid, source) + ).pipe(retryBackoff(500, e => this.log(e))) + ); + } + } + + async remove_subclass_relationships(dataset_uuid, config) { + if(config.length <= 0) return; + + for (const src of config){ + await this.cdb.class_remove_subclass(dataset_uuid, src); + this.log(`Removed ${dataset_uuid} from ${src} subclasses.`); + } + } +} \ No newline at end of file diff --git a/acs-data-access/lib/utils.js b/acs-data-access/lib/utils.js index ac6430d17..d7987e5a7 100644 --- a/acs-data-access/lib/utils.js +++ b/acs-data-access/lib/utils.js @@ -1,3 +1,10 @@ +import { APIError } from "@amrc-factoryplus/service-api"; +export function fail(log, status, message) { + log(message); + throw new APIError(status); +} + + export function csv_escape(value) { if (value == null) return ""; @@ -35,4 +42,5 @@ export function minDate(a, b) { return new Date(a) < new Date(b) ? a : b -} \ No newline at end of file +} + From 8c416793b34ace1235acdb04d2b8c883a7bd4718 Mon Sep 17 00:00:00 2001 From: amrc-za Date: Fri, 12 Jun 2026 14:31:03 +0100 Subject: [PATCH 2/2] data access updated tests --- .../tests/http/delete_dataset.test.js | 26 ++++---- .../tests/{current => http}/download.test.js | 0 .../tests/http/metadata_list.test.js | 2 +- .../tests/http/metadata_single.test.js | 26 ++------ .../tests/http/single_structure.test.js | 66 ++++++++++++++++--- .../tests/http/structure_create.test.js | 51 ++++++-------- .../tests/http/structure_list.test.js | 2 +- .../tests/http/structure_update.test.js | 8 +-- acs-data-access/tests/tst-http-api.js | 10 +-- 9 files changed, 109 insertions(+), 82 deletions(-) rename acs-data-access/tests/{current => http}/download.test.js (100%) diff --git a/acs-data-access/tests/http/delete_dataset.test.js b/acs-data-access/tests/http/delete_dataset.test.js index 1f20e5f1c..734c1154c 100644 --- a/acs-data-access/tests/http/delete_dataset.test.js +++ b/acs-data-access/tests/http/delete_dataset.test.js @@ -16,7 +16,6 @@ describe('GET /delete/:uuid', () => { let admin_fplus; let ALL_GRANTS = []; - let dataset_to_be_deleted; beforeEach(async () => { admin_fplus = new ServiceClient({ @@ -43,11 +42,6 @@ describe('GET /delete/:uuid', () => { password: process.env.ADMIN_PASSWORD, verbose: 'ALL' }); - - - dataset_to_be_deleted = await admin_fplus.DataAccess.create_dataset(Constants.App.SparkplugSrc, Temp_Data.Valid_Body.SRC.config); - - console.log(`Created new dataset ${dataset_to_be_deleted}`); }); // Clean up grants if not removed in case of failure. @@ -83,24 +77,32 @@ describe('GET /delete/:uuid', () => { test('Successful deletion', async () => { + const dataset_uuid = await admin_fplus.DataAccess.create_dataset(Constants.App.SparkplugSrc, Temp_Data.Valid_Body.SRC.config); + + await sleep(4000); + const grant = await addGrant( TEST_PRINCIPAL, Constants.Perm.DeleteDataset, - dataset_to_be_deleted, + dataset_uuid, false ); - await sleep(10000); + await sleep(4000); - const deleted = await test_fplus.DataAccess.delete_dataset(dataset_to_be_deleted); + const deleted = await test_fplus.DataAccess.delete_dataset(dataset_uuid); + + await sleep(4000); await deleteGrant(grant); - expect(deleted).toBe(dataset_to_be_deleted); + expect(deleted).toBe(dataset_uuid); }, 30000); test('Irrelevant permission', async () => { + const dataset_to_be_deleted = Temp_Data.Test_Uuids.Src_Datasets.Node2_TestDoubleDeviceDataset; + const grant = await addGrant( TEST_PRINCIPAL, Constants.Perm.EditDataset, @@ -116,7 +118,7 @@ describe('GET /delete/:uuid', () => { expect.fail('Expected to throw'); } catch (err) { - expect(err.status).not.toBe(200); + expect(err.status).toBe(403); } await deleteGrant(grant); @@ -131,7 +133,7 @@ describe('GET /delete/:uuid', () => { expect.fail('Expected to throw'); } catch (err) { - expect(err.status).not.toBe(200); + expect(err.status).toBe(422); } }, 30000); }); \ No newline at end of file diff --git a/acs-data-access/tests/current/download.test.js b/acs-data-access/tests/http/download.test.js similarity index 100% rename from acs-data-access/tests/current/download.test.js rename to acs-data-access/tests/http/download.test.js diff --git a/acs-data-access/tests/http/metadata_list.test.js b/acs-data-access/tests/http/metadata_list.test.js index 5e1336bcf..bba4318b2 100644 --- a/acs-data-access/tests/http/metadata_list.test.js +++ b/acs-data-access/tests/http/metadata_list.test.js @@ -2,7 +2,7 @@ import {ServiceClient} from "@amrc-factoryplus/service-client"; import process from "node:process"; import {afterAll, beforeAll, beforeEach, describe, expect, test} from 'vitest'; import { DataAccess as Constants } from "../../lib/constants.js"; -import * as Temp_Data from '../temp_data.js'; +import {Test_Uuids} from '../temp_data.js'; function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); diff --git a/acs-data-access/tests/http/metadata_single.test.js b/acs-data-access/tests/http/metadata_single.test.js index 6f30aae98..2e9be03ed 100644 --- a/acs-data-access/tests/http/metadata_single.test.js +++ b/acs-data-access/tests/http/metadata_single.test.js @@ -91,10 +91,11 @@ describe('GET /metadata/:uuid', () => { try { const res = await test_fplus.DataAccess.get_single_metadata(testCase); + expect.fail('Expected to throw'); } catch (err) { - expect(err.status).not.toBe(200); + expect(err.status).toBe(403); } }); @@ -113,7 +114,7 @@ describe('GET /metadata/:uuid', () => { expect.fail('Expected to throw'); } catch (err) { - expect(err.status).not.toBe(200); + expect(err.status).toBe(403); } finally{ await deleteGrant(grant); @@ -129,7 +130,7 @@ describe('GET /metadata/:uuid', () => { expect.fail('Expected to throw'); } catch (err) { - expect(err.status).not.toBe(200); + expect(err.status).toBe(422); } }); @@ -147,7 +148,6 @@ describe('GET /metadata/:uuid', () => { await sleep(3600); - const res_valid = await test_fplus.DataAccess.get_single_metadata(testCase); // make testCase invalid await addConfig( @@ -155,14 +155,9 @@ describe('GET /metadata/:uuid', () => { testCase, [] ); - await sleep(3600); + await sleep(4000); - try{ - await test_fplus.DataAccess.get_single_metadata(testCase); - expect.fail('Expected to throw'); - }catch(err){ - expect(err.status).not.toBe(200); - } + const res = await test_fplus.DataAccess.get_single_metadata(testCase); // make testCase valid await removeConfig(invalidApp, testCase); @@ -170,8 +165,7 @@ describe('GET /metadata/:uuid', () => { // revoke grant await deleteGrant(grant); - expect(res_valid).toHaveProperty('uuid'); - + expect(res).toEqual({}); }, 40000); @@ -198,8 +192,6 @@ describe('GET /metadata/:uuid', () => { const res = await test_fplus.DataAccess.get_single_metadata(testCase); - console.log(res); - await deleteGrant(grant); expect(res).toEqual(expect.objectContaining({ @@ -236,8 +228,6 @@ describe('GET /metadata/:uuid', () => { const res = await test_fplus.DataAccess.get_single_metadata(testCase); - console.log(res); - await deleteGrant(grant); expect(res).not.haveOwnProperty('from'); @@ -288,8 +278,6 @@ describe('GET /metadata/:uuid', () => { const res = await test_fplus.DataAccess.get_single_metadata(testCase); - console.log(res); - await deleteGrant(grant); diff --git a/acs-data-access/tests/http/single_structure.test.js b/acs-data-access/tests/http/single_structure.test.js index 711cf4ec2..45ef14b4d 100644 --- a/acs-data-access/tests/http/single_structure.test.js +++ b/acs-data-access/tests/http/single_structure.test.js @@ -86,7 +86,7 @@ describe('GET /structure/:uuid', () => { await admin_fplus.ConfigDB.delete_config( app, obj ); } - test('Dataset exist but no permission', async () => { + test('Dataset exists but no permission', async () => { const testCase = Temp_Data.Test_Uuids.Src_Datasets.TestDeviceDataset; try { @@ -94,7 +94,7 @@ describe('GET /structure/:uuid', () => { expect.fail('Expected to throw'); } catch (err) { - expect(err.status).not.toBe(200); + expect(err.status).toBe(403); } }); @@ -113,7 +113,7 @@ describe('GET /structure/:uuid', () => { expect.fail('Expected to throw'); } catch (err) { - expect(err.status).not.toBe(200); + expect(err.status).toBe(403); } finally{ await deleteGrant(grant); @@ -128,7 +128,7 @@ describe('GET /structure/:uuid', () => { expect.fail('Expected to throw'); } catch (err) { - expect(err.status).not.toBe(200); + expect(err.status).toBe(422); } }); @@ -147,7 +147,6 @@ describe('GET /structure/:uuid', () => { await sleep(3600); - // make testCase invalid await addConfig( invalidApp, @@ -156,12 +155,9 @@ describe('GET /structure/:uuid', () => { ); await sleep(3600); - const res_invalid = await test_fplus.DataAccess.get_single_structure(testCase); await sleep(1000); - - await removeConfig(invalidApp, testCase); await deleteGrant(grant); @@ -171,10 +167,9 @@ describe('GET /structure/:uuid', () => { }, 40000); - test('SRC', async () => { + test('SRC format', async () => { const testCase = Temp_Data.Test_Uuids.Src_Datasets.Node2_TestDoubleDeviceDataset; - // Grant const grant = await addGrant( TEST_PRINCIPAL, @@ -196,4 +191,55 @@ describe('GET /structure/:uuid', () => { expect(res.config).toHaveProperty('source'); }, 40000); + test('Session format', async () => { + const testCase = Temp_Data.Test_Uuids.Session_Datasets.SessionNode2DoubleDataset; + + // Grant + const grant = await addGrant( + TEST_PRINCIPAL, + Constants.Perm.EditDataset, + testCase, + false + ); + + await sleep(3600); + + const res = await test_fplus.DataAccess.get_single_structure(testCase); + console.log(res); + + + await deleteGrant(grant); + + expect(res.structure).toEqual(Constants.App.SessionLimits); + expect(res.config).not.toBe(null); + expect(res.config).toHaveProperty('source'); + expect(res.config).toHaveProperty('from'); + expect(res.config).toHaveProperty('to'); + }, 40000); + + + test('Union format', async () => { + const testCase = Temp_Data.Test_Uuids.Union_Datasets.TestUnionAllDataset; + + // Grant + const grant = await addGrant( + TEST_PRINCIPAL, + Constants.Perm.EditDataset, + testCase, + false + ); + + await sleep(3600); + + const res = await test_fplus.DataAccess.get_single_structure(testCase); + console.log(res); + + + await deleteGrant(grant); + + expect(res.structure).toEqual(Constants.App.UnionComponents); + expect(res.config).not.toBe(null); + expect(res.config).toBeInstanceOf(Array); + }, 40000); + }); \ No newline at end of file diff --git a/acs-data-access/tests/http/structure_create.test.js b/acs-data-access/tests/http/structure_create.test.js index bf2d4c396..dede65654 100644 --- a/acs-data-access/tests/http/structure_create.test.js +++ b/acs-data-access/tests/http/structure_create.test.js @@ -86,21 +86,21 @@ describe('POST /structure', () => { - test('User has no CreateDataset permission', async () => { + test('No permission', async () => { try { await test_fplus.DataAccess.create_dataset( Temp_Data.Valid_Body.SRC.structure, Temp_Data.Valid_Body.SRC.config ); - expect.fail('Expected create_dataset to throw'); + expect.fail('Expected to throw'); } catch (err) { - expect(err.status).not.toBe(200); + expect(err.status).toBe(403); } }); - test('User has CreateDataset permission for irrelevant structure', async () => { + test('Correct permission for irrelevant structure', async () => { const irrelevant_structure = Constants.App.SessionLimits; const irrelevant_grant = await addGrant( @@ -109,7 +109,7 @@ describe('POST /structure', () => { irrelevant_structure, false ); - await sleep(500); + await sleep(4000); try { await test_fplus.DataAccess.create_dataset( @@ -117,10 +117,10 @@ describe('POST /structure', () => { Temp_Data.Valid_Body.Session.config ); - expect.fail('Expected create_dataset to throw'); + expect.fail('Expected to throw'); } catch (err) { - expect(err.status).not.toBe(200); + expect(err.status).toBe(403); } await deleteGrant(irrelevant_grant); @@ -132,11 +132,11 @@ describe('POST /structure', () => { try { await test_fplus.DataAccess.create_dataset(testCase.structure, testCase.config); - expect.fail('Expected create_dataset to throw'); + expect.fail('Expected to throw'); } catch (err) { - expect(err.status).not.toBe(200); + expect(err.status).toBe(422); } }); @@ -160,12 +160,12 @@ describe('POST /structure', () => { await deleteGrant(grant); } catch(err){ console.log(err); - expect(err.status).not.toBe(200); + expect(err.status).toBe(403); } }); - test.each(ALL_VALID_BODY_CASES)('Create - case %#', async (testCase) => { + test.each(ALL_VALID_BODY_CASES)('Successful create - case %#', async (testCase) => { let grants = []; @@ -178,7 +178,7 @@ describe('POST /structure', () => { ); grants.push(grant_uuid); - await sleep(500); + await sleep(4000); if(testCase.structure === Constants.App.UnionComponents){ @@ -200,24 +200,19 @@ describe('POST /structure', () => { false ); grants.push(g); - await sleep(1000); } + await sleep(4000); const dataset_uuid = await test_fplus.DataAccess.create_dataset( testCase.structure, testCase.config ); - - console.log("HERE IS THE NEW OBJECT CREATED: ", dataset_uuid); + await sleep(2000); for(let grant of grants){ await deleteGrant(grant); - await sleep(1000); } - - // await deleteConfigDBObj(obj_uuid); await deleteDataset(dataset_uuid); - await sleep(1000); expect(dataset_uuid).not.toBe(undefined); }, 30000); @@ -239,7 +234,7 @@ describe('POST /structure', () => { ); grants.push(create_grant); - await sleep(1000); + await sleep(4000); const scnd_lvl_grant = await addGrant( TEST_PRINCIPAL, @@ -249,10 +244,11 @@ describe('POST /structure', () => { ); grants.push(scnd_lvl_grant); - await sleep(3000); + await sleep(4000); // 2. dataset_uuid = create session dataset const dataset_uuid = await test_fplus.DataAccess.create_dataset(testCase.structure, testCase.config); + await sleep(2000); // 3. query the source's subclasses const subclasses = await admin_fplus.ConfigDB.class_subclasses(testCase.config.source); @@ -260,7 +256,6 @@ describe('POST /structure', () => { // 4. revoke grants for(let grant of grants){ await deleteGrant(grant); - await sleep(1000); } // 5. Delete dataset @@ -284,7 +279,8 @@ describe('POST /structure', () => { ); grants.push(create_grant); - await sleep(500); + await sleep(4000); + for (const s of testCase.config){ const g = await addGrant( TEST_PRINCIPAL, @@ -293,27 +289,22 @@ describe('POST /structure', () => { false ); grants.push(g); - await sleep(100); } - + await sleep(4000); // 2. dataset_uuid = create dataset const dataset_uuid = await test_fplus.DataAccess.create_dataset(testCase.structure, testCase.config); - await sleep(1000); + await sleep(2000); // 3. query the dataset's subclasses const subclasses = await admin_fplus.ConfigDB.class_subclasses(dataset_uuid); - await sleep(500); - // 4. revoke grants for(let grant of grants){ await deleteGrant(grant); - await sleep(1000); } - // 5. delete dataset await deleteDataset(dataset_uuid); diff --git a/acs-data-access/tests/http/structure_list.test.js b/acs-data-access/tests/http/structure_list.test.js index 9678d909c..87bf39e33 100644 --- a/acs-data-access/tests/http/structure_list.test.js +++ b/acs-data-access/tests/http/structure_list.test.js @@ -2,7 +2,7 @@ import {ServiceClient} from "@amrc-factoryplus/service-client"; import process from "node:process"; import {afterAll, beforeAll, beforeEach, describe, expect, test} from 'vitest'; import { DataAccess as Constants } from "../../lib/constants.js"; -import * as Temp_Data from '../temp_data.js'; +import { Test_Uuids } from "../temp_data.js"; function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); diff --git a/acs-data-access/tests/http/structure_update.test.js b/acs-data-access/tests/http/structure_update.test.js index 3ed64c91f..23310ec55 100644 --- a/acs-data-access/tests/http/structure_update.test.js +++ b/acs-data-access/tests/http/structure_update.test.js @@ -139,7 +139,7 @@ describe('PUT /structure', () => { expect.fail('Expected to throw'); } catch (err) { - expect(err.status).not.toBe(200); + expect(err.status).toBe(403); } await deleteGrant(grant); }); @@ -156,7 +156,7 @@ describe('PUT /structure', () => { expect.fail('Expected to throw'); } catch (err) { - expect(err.status).not.toBe(200); + expect(err.status).toBe(422); } }); @@ -167,10 +167,10 @@ describe('PUT /structure', () => { testCase.structure, testCase.config ); - expect.fail('Expected create_dataset to throw'); + expect.fail('Expected to throw'); } catch (err) { - expect(err.status).not.toBe(200); + expect(err.status).toBe(422); } }, 30000); diff --git a/acs-data-access/tests/tst-http-api.js b/acs-data-access/tests/tst-http-api.js index 893236ced..8577fc026 100644 --- a/acs-data-access/tests/tst-http-api.js +++ b/acs-data-access/tests/tst-http-api.js @@ -15,19 +15,19 @@ import process from "node:process"; async function main(){ const fplus = new ServiceClient({ directory_url: process.env.DIRECTORY_URL, - username: process.env.TEST_HUMAN_USERNAME, - password: process.env.TEST_HUMAN_PASSWORD, + username: process.env.HUMAN_USERNAME, + password: process.env.HUMAN_PASSWORD, verbose: 'ALL' }); - const uuid = "a7594958-4b03-45e4-8ac0-4af8d1e77e3f"; - const res = await fplus.DataAccess.delete_dataset(uuid); + const uuid = "e2a4c530-dc0f-417d-b00b-329b0e90e033"; + // const res = await fplus.DataAccess.delete_dataset(uuid); - // const res = await fplus.DataAccess.download_data(DATASET_UUID) + const res = await fplus.DataAccess.download_data(uuid); // const res = await fplus.DataAccess.get_metadata_list();